Load libraries
library(signal)
library(tidyverse)
library(here)
library(lubridate)
library(dtplyr)
library(sf)
library(knitr)
library(mgcv)
library(future)
library(furrr)
library(progressr)
library(pracma)
Set a simple console progress bar
handlers("txtprogressbar") # Simple console progress bar
Supress package messages
suppressPackageStartupMessages({
library(mgcv)
library(nlme)
})
Load previously created objects
load(file = "objects/data_RS_S2_bands_indices.Rdata")
load(file = "objects/GAM_data_S2.Rdata")
load(file = "objects/smoothed_data_S2.Rdata")
load(file = "objects/monthly_avg_indices_S2.Rdata")
Load Resurvey db
db_Europa_allobs <- read_csv(
here("data", "clean", "db_Europa_allobs.csv")) %>%
select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
EUNISa_2, EUNISa_2_descr) %>%
mutate(PlotObservationID = factor(PlotObservationID),
EUNISa_1 = factor(EUNISa_1), EUNISa_2 = factor(EUNISa_2))
Define printall function
printall <- function(tibble) {
print(tibble, width = Inf)
}
Read files with band data
I got these files using the GEE code prepared by Bea.
These files contain all observations in the ReSurvey database from
2016 onward. In order to avoid computation problems in GEE,
biogeographical units that contain more than 4500 points have been
subdivided in ArcGIS.
# Set the folder path
folder_path <- "C:/Data/MOTIVATE/MOTIVATE_RS_data/S2/Bands/all"
# List CSV files
csv_files <- list.files(folder_path, full.names = TRUE, recursive = TRUE)
# Function to extract biogeo and unit from the filename
extract_info <- function(filename) {
first_word <- strsplit(filename, "_")[[1]][1]
biogeo <- str_extract(first_word,
"^(ALP|ANA|ARC|ATL|BLACKSEA|BOR|CON|MACARONESIA|MED|PANONIA|STEPPIC)")
unit <- str_remove(first_word, biogeo)
if (is.na(unit) || unit == "") unit <- NA_character_
list(biogeo = biogeo, unit = unit)
}
# Define column types: force RSrvypl to character, others auto-detected
custom_col_types <- cols(
RSrvypl = col_character(),
RSrvyst = col_character(),
default = col_guess()
)
# Read and process each file
data_list <- lapply(csv_files, function(file) {
info <- extract_info(basename(file)) # Use only the filename
# Read the file
df <- read_csv(file, col_types = custom_col_types) %>%
# Remove columns that give column type problems when combining data
select(-starts_with("EUNIS"), -starts_with("ReSurvey")) %>%
mutate(biogeo = info$biogeo, unit = info$unit)
return(df)
})
# Combine all data
data_RS_S2_bands <- bind_rows(data_list) %>%
rename(PlotObservationID = PltObID)
# View the resulting tibble
print(data_RS_S2_bands)
# Counts per biogeo and unit
print(data_RS_S2_bands %>% count(biogeo, unit), n = 100)
Some checks
Check that the year in the date of the images is not different to the
sampling year:
data_RS_S2_bands %>% dplyr::filter(year != year(date))
Check how many different images are for each observation, date and
time:
data_RS_S2_bands %>% group_by(PlotObservationID, date, time_utc) %>%
summarise(n_images = n_distinct(image_id), .groups = "drop") %>%
count(n_images)
Average the bands
When there is more than one image for each point and day, average the
values of the bands:
Calculate indices
Save:
Plot n_daytime:
data_RS_S2_bands_indices %>%
group_by(PlotObservationID) %>%
summarise(n_days = first(n_days)) %>% ungroup() %>%
ggplot(aes(x = n_days)) + geom_histogram(color = "black", fill = "white") +
theme_minimal()

Compute phenological metrics from models fitted to time series
data
Function
HERE (already done in Landsat): Modify function and rerun everything
from here (TBD)
Using GAMs, reweighting and 3 iterations.
Using both a change detection method (maximum slope) and a threshold
method (50% amplitude) to calculate sos and eos.
Approach similar to https://doi.org/10.1016/j.jag.2020.102172 for GAM
fitting and change detection method, and to https://www.mdpi.com/2072-4292/12/22/3738 fot threshold
method.
Define function to compute phenology metrics using GAM fit and NDVI /
EVI / SAVI:
compute_metrics_models <- function(df, index_cols = c("NDVI", "EVI", "SAVI")) {
suppressPackageStartupMessages({
library(mgcv)
library(nlme)
})
plan(multisession) # Set up parallel processing
# Create a list of index-specific data frames
index_dfs <- lapply(index_cols, function(index_col) {
list(index_col = index_col, df = df %>%
select(DOY, PlotObservationID, all_of(index_col)))
})
# Define the processing function for each index
process_index <- function(index_data) {
index_col <- index_data$index_col
df_index <- index_data$df %>%
filter(!is.na(.data[[index_col]])) %>%
arrange(DOY)
plot_id <- unique(df_index$PlotObservationID)
if (nrow(df_index) < 10) {
message(" Skipped: insufficient data (< 10 rows)")
return(tibble(PlotObservationID = plot_id, index = index_col,
sos_slope = NA_real_, sos_threshold = NA_real_,
pos = NA_real_, eos_slope = NA_real_,
eos_threshold = NA_real_, auc_slope = NA_real_,
auc_threshold = NA_real_, Vmax = NA_real_,
DOY = df_index$DOY, value = NA_real_))
}
# Replace early/late DOY values
base_value_early <- mean(df_index %>% filter(DOY <= 50) %>%
pull(index_col), na.rm = TRUE)
base_value_late <- mean(df_index %>% filter(DOY >= 315) %>%
pull(index_col), na.rm = TRUE)
df_index <- df_index %>%
mutate(!!index_col := case_when(
DOY <= 50 ~ base_value_early,
DOY >= 315 ~ base_value_late,
TRUE ~ .data[[index_col]]
))
x <- df_index$DOY
y <- df_index[[index_col]]
weights <- rep(1, length(y))
# GAM fit
pred <- NULL
for (i in 1:3) {
gam_fit <- tryCatch({
mgcv::bam(y ~ s(x, bs = "tp"),weights = weights)
}, error = function(e) {
message(" GAM fitting failed for ", plot_id, " - ", index_col, ": ",
e$message)
return(NULL)
})
if (is.null(gam_fit)) {
return(tibble(
PlotObservationID = plot_id,
index = index_col,
sos_slope = NA_real_,
sos_threshold = NA_real_,
pos = NA_real_,
eos_slope = NA_real_,
eos_threshold = NA_real_,
auc_slope = NA_real_,
auc_threshold = NA_real_,
Vmin_pre = NA_real_,
Vmin_post = NA_real_,
Vmax = NA_real_,
u_sos = NA_real_,
u_eos = NA_real_,
DOY = df_index$DOY,
value = NA_real_))
}
pred <- tryCatch({
predict(gam_fit, newdata = tibble(x = x))
}, error = function(e) {
message("Prediction failed for ", plot_id, " - ", index_col, ": ",
e$message)
return(rep(NA_real_, length(x)))
})
idx_between <- which(x > 50 & x < 315 & !is.na(pred) & pred != 0)
weights <- rep(1, length(y))
weights[idx_between] <- (y[idx_between] / (pred[idx_between] + 1e-6))^4
weights[weights > 1 | is.na(weights)] <- 1
}
# Compute metrics
slope <- c(NA, diff(pred))
idx <- which(x >= 50 & x <= 315)
pos <- if (length(idx) > 0) x[idx][which.max(pred[idx])] else NA_real_
sos_slope <- if (!is.na(pos)) {
idx <- which(x < pos)
if (length(idx) > 0) x[idx][which.max(slope[idx])] else NA_real_
} else NA_real_
eos_slope <- if (!is.na(pos)) {
idx <- which(x > pos)
if (length(idx) > 0) x[idx][which.min(slope[idx])] else NA_real_
} else NA_real_
integration_idx_slope <- which(x >= sos_slope & x <=
eos_slope & !is.na(pred))
auc_slope <- if (length(integration_idx_slope) > 1) {
sum(diff(x[integration_idx_slope]) *
zoo::rollmean(pred[integration_idx_slope], 2))
} else NA_real_
# Vmin antes y después del pico
Vmin_pre <- if (!is.na(pos)) min(pred[x <= pos], na.rm = TRUE)else NA_real_
Vmin_post <- if (!is.na(pos)) min(pred[x >= pos], na.rm = TRUE) else NA_real_
Vmax <- max(pred, na.rm = TRUE)
# Umbrales relativos
p <- 0.5
u_sos <- if (!is.na(Vmin_pre)) Vmin_pre + p * (Vmax - Vmin_pre) else NA_real_
u_eos <- if (!is.na(Vmin_post)) Vmin_post + p * (Vmax - Vmin_post) else NA_real_
# DOY donde se cruzan los umbrales
sos_threshold <- if (!is.na(u_sos)) x[which(pred >= u_sos)[1]] else NA_real_
eos_threshold <- if (!is.na(u_eos)) x[rev(which(pred >= u_eos))[1]] else NA_real_
integration_idx_threshold <- which(x >= sos_threshold &
x <= eos_threshold & !is.na(pred))
auc_threshold <- if (length(integration_idx_threshold) > 1) {
sum(diff(x[integration_idx_threshold]) *
zoo::rollmean(pred[integration_idx_threshold], 2))
} else NA_real_
# 1. Predicciones por DOY
fits_df <- tibble(
PlotObservationID = unique(df_index$PlotObservationID),
DOY = x,
value = pred,
index = index_col
)
# 2. Métricas resumen
metrics_df <- tibble(
PlotObservationID = unique(df_index$PlotObservationID),
index = index_col,
sos_slope = sos_slope,
sos_threshold = sos_threshold,
pos = pos,
eos_slope = eos_slope,
eos_threshold = eos_threshold,
auc_slope = auc_slope,
auc_threshold = auc_threshold,
Vmin_pre = Vmin_pre,
Vmin_post = Vmin_post,
Vmax = Vmax,
u_sos = u_sos,
u_eos = u_eos
)
# 3. Unir por PlotObservationID, index
final_df <- left_join(fits_df, metrics_df,
by = c("PlotObservationID", "index"))
}
# Run in parallel
results <- future_map(index_dfs, process_index, .progress = TRUE)
results <- purrr::compact(results) # removes NULLs
if (length(results) == 0) return(tibble()) # or return(NULL)
bind_rows(results)
}
Calculation
Apply the function with batch processing
plan(sequential)
Save
Look:
GAM_data
Save as an object:
Assess time series quality
For the time series to be acceptable, it should have a reasonable
number of time points, and these points should be distributed along
almost all months (could be ok to miss the winter months).
In GAM data, check how many time points are there for each
PlotObservationID, how many months, and which months are missing.
ts_quality <- GAM_data %>%
# Filter only NDVI (all indices will have the same time points)
dplyr::filter(index == "NDVI") %>%
# Get month from DOY
mutate(month = month(ymd("2020-01-01") + days(DOY - 1))) %>%
# For each PlotObservationID
group_by(PlotObservationID) %>%
# Get the number of time points (days) and the number of months
summarise(
n_days = n_distinct(DOY),
n_months = n_distinct(month),
.groups = "drop"
) %>%
left_join(GAM_data %>%
# Filter only NDVI
dplyr::filter(index == "NDVI") %>%
# Get month from DOY
mutate(month = month(ymd("2020-01-01") + days(DOY - 1))) %>%
# Get unique values of PlotObservationID and month
distinct(PlotObservationID, month) %>%
# Add 1 as value
mutate(value = 1) %>%
# Reshape to wide format and add zeros when month is missing
pivot_wider(
names_from = month,
names_prefix = "month",
values_from = value,
values_fill = 0),
by = "PlotObservationID")
Histograms time points and n months:
ggplot(ts_quality, aes(x = n_days)) +
geom_histogram(color = "black", fill = "white") +
xlab("Number of time points (days) in the S2 time series") +
theme_minimal()

ggplot(ts_quality, aes(x = n_months)) +
geom_histogram(color = "black", fill = "white") +
xlab("Number of months in the S2 time series") +
theme_minimal()

Count how many PlotObservationIDs have missing data (value 0) for
each month:
obs_missing_month <- ts_quality %>%
summarise(across(starts_with("month"), ~ sum(.x == 0))) %>%
pivot_longer(cols = everything(), names_to = "month", values_to = "nobs_missing")
ggplot(obs_missing_month %>%
mutate(month = factor(month, levels = paste0("month", 1:12))),
aes(x = month, y = nobs_missing)) + geom_bar(stat = "identity") +
ylab("Number of PlotObservationID with missing data") +
ggtitle("Missing data in S2 time series") +
theme_minimal()

Add quality flag:
ts_quality_flag <- ts_quality %>%
rowwise() %>%
mutate(
# If 2 consecutive months of the period March-October are missing
# quality_flag = 0
quality_flag = {
months <- c_across(month3:month10)
if (any(months[-length(months)] == 0 & months[-1] == 0)) 0 else 1
}
) %>%
ungroup()
ts_quality_flag %>% count(quality_flag)
Boxplot comparing moments for different indices
GAM_data %>%
select(PlotObservationID, index, sos_slope, sos_threshold, pos, eos_slope,
eos_threshold) %>% distinct() %>%
pivot_longer(cols = c(sos_slope, sos_threshold, pos, eos_slope, eos_threshold),
names_to = "moment", values_to = "value") %>%
ggplot(aes(x = moment, y = value, fill = index)) + geom_boxplot() +
theme_minimal()

Plot fit and moments for each PlotObservationID
Quality = 1
# Get unique IDs with quality_flag == 1
ids_q1 <- ts_quality_flag %>%
dplyr::filter(quality_flag == 1) %>%
mutate(PlotObservationID = droplevels(PlotObservationID)) %>%
pull(PlotObservationID)
GAM_data_ids_q1 <- GAM_data %>%
# Join to get biogeo and unit
left_join(data_RS_S2_bands_indices %>%
select(PlotObservationID, biogeo, unit) %>%
distinct()) %>%
# Join to get EUNIS info
left_join(db_Europa_allobs %>%
select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
EUNISa_2, EUNISa_2_descr)) %>%
# Join to get original values of indices
left_join(data_RS_S2_bands_indices %>%
select(PlotObservationID, DOY, NDVI, EVI, SAVI) %>%
pivot_longer(cols = c(NDVI, EVI, SAVI), names_to = "index",
values_to = "value_orig")) %>%
# Join to get ts_quality data
left_join(ts_quality_flag %>% select(PlotObservationID, quality_flag)) %>%
# Keep only those with quality_flag == 1
dplyr::filter(quality_flag == 1)
Save each plot to a file:
Quality = 0
# Get unique IDs with quality_flag == 0
ids_q0 <- ts_quality_flag %>%
dplyr::filter(quality_flag == 0) %>%
mutate(PlotObservationID = droplevels(PlotObservationID)) %>%
pull(PlotObservationID)
GAM_data_ids_q0 <- GAM_data %>%
# Join to get biogeo and unit
left_join(data_RS_S2_bands_indices %>%
select(PlotObservationID, biogeo, unit) %>%
distinct()) %>%
# Join to get EUNIS info
left_join(db_Europa_allobs %>%
select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
EUNISa_2, EUNISa_2_descr)) %>%
# Join to get original values of indices
left_join(data_RS_S2_bands_indices %>%
select(PlotObservationID, DOY, NDVI, EVI, SAVI) %>%
pivot_longer(cols = c(NDVI, EVI, SAVI), names_to = "index",
values_to = "value_orig")) %>%
# Join to get ts_quality data
left_join(ts_quality_flag %>%
select(PlotObservationID, n_months, quality_flag)) %>%
# Keep only those with quality_flag == 0
dplyr::filter(quality_flag == 0)
Save each plot to a file:
Smooth the time series of NDMI and NDWI
Using GAM, without replacing values in DOY 1–50 and DOY 315–end with
separate base values, later use only unweighted GAM.
compute_unweighted_fit <- function(
# Data frame df with index values over time (DOY)
df,
# Name of the vegetation indices columns (e.g., "NDVI", "EVI", "SAVI)
index_cols = c("NDMI", "NDWI")
) {
# Initialize list to store results
fits_list <- list()
# Loop over each index column
for (index_col in index_cols) {
df_index <- df %>%
# Remove rows with missing index values and sort data by DOY
filter(!is.na(.data[[index_col]])) %>% arrange(DOY)
# Extract x (DOY) and y (index) vectors for modelling
x <- df_index$DOY
y <- df_index[[index_col]]
# If there are fewer than 11 observations or all values are NA, skip
if (length(x) < 11 || all(is.na(y))) {
next
}
# Fit GAM (unweighted) with a thin plate spline (bs = "tp")
# to smooth the index curve
gam_unweighted <- mgcv::bam(y ~ s(x, bs = "tp"))
pred <- predict(gam_unweighted, newdata = tibble(x = x))
# Create tibble to store original and predicted index values
fits_df <- tibble(
PlotObservationID = unique(df$PlotObservationID),
DOY = x,
index = index_col,
value = pred
)
fits_list[[index_col]] <- fits_df
}
if (length(fits_list) == 0) {
return(tibble())
}
bind_rows(fits_list)
}
Apply the function:
Look:
smoothed_data
Save as an object:
Plot fit and moments for each PlotObservationID
smoothed_data_ids <- smoothed_data %>%
# Join to get biogeo and unit
left_join(data_RS_S2_bands_indices %>%
select(PlotObservationID, biogeo, unit) %>%
distinct()) %>%
# Join to get EUNIS info
left_join(db_Europa_allobs %>%
select(PlotObservationID, EUNISa_1, EUNISa_1_descr,
EUNISa_2, EUNISa_2_descr)) %>%
mutate(PlotObservationID = as.character(PlotObservationID))
Save each plot to a file:
Get indices data (max. and min.)
Careful! These maximum and minimum values are from the smoothed time
series. For NDVI / EVI / SAVI values in DOY 1–50 and DOY 315–end,
remember that the GAM smoothing function replaced the original values
with the mean base value of observations during each of these respective
periods. This was so far not done for NDMI and NDWI.
final_indices_data <- GAM_data %>%
group_by(PlotObservationID, index) %>%
summarise(max = max(value), min = min(value)) %>%
ungroup() %>%
pivot_wider(names_from = index, values_from = c(max, min),
names_glue = "{index}_{.value}") %>%
full_join(
smoothed_data %>%
group_by(PlotObservationID, index) %>%
summarise(max = max(value), min = min(value)) %>%
ungroup() %>%
pivot_wider(names_from = index, values_from = c(max, min),
names_glue = "{index}_{.value}")
)
Get phenology data
Use GAM iter_3 to get dates of the moments, values at those moments
and AUC (time-integrated indices) between SOS and EOS:
# Join to get values at SOS, POS, EOS and auc
final_phenology_data <- GAM_data %>%
mutate(
stage = case_when(
DOY == sos_slope ~ "sos_slope",
DOY == sos_threshold ~ "sos_threshold",
DOY == pos ~ "pos",
DOY == eos_slope ~ "eos_slope",
DOY == eos_threshold ~ "eos_threshold",
TRUE ~ NA_character_
)
) %>%
dplyr::filter(!is.na(stage)) %>%
select(PlotObservationID, index, stage, doy = DOY, value) %>%
pivot_wider(
names_from = c(index, stage),
values_from = c(doy, value),
names_glue = "{index}_{stage}_{.value}"
) %>%
# Convert list cols to regular numeric cols
mutate(
NDVI_sos_slope_value = map_dbl(NDVI_sos_slope_value, 1),
NDVI_sos_threshold_value = map_dbl(NDVI_sos_threshold_value, 1),
NDVI_pos_value = map_dbl(NDVI_pos_value, 1),
NDVI_eos_slope_value = map_dbl(NDVI_eos_slope_value, 1),
NDVI_eos_threshold_value = map_dbl(NDVI_eos_threshold_value, 1),
EVI_sos_slope_value = map_dbl(EVI_sos_slope_value, 1),
EVI_sos_threshold_value = map_dbl(EVI_sos_threshold_value, 1),
EVI_pos_value = map_dbl(EVI_pos_value, 1),
EVI_eos_slope_value = map_dbl(EVI_eos_slope_value, 1),
EVI_eos_threshold_value = map_dbl(EVI_eos_threshold_value, 1),
SAVI_sos_slope_value = map_dbl(SAVI_sos_slope_value, 1),
SAVI_sos_threshold_value = map_dbl(SAVI_sos_threshold_value, 1),
SAVI_pos_value = map_dbl(SAVI_pos_value, 1),
SAVI_eos_slope_value = map_dbl(SAVI_eos_slope_value, 1),
SAVI_eos_threshold_value = map_dbl(SAVI_eos_threshold_value, 1)
) %>%
full_join(GAM_data %>%
distinct(PlotObservationID, index, auc_slope, auc_threshold) %>%
pivot_wider(names_from = index, values_from = c(auc_slope, auc_threshold),
names_glue = "{index}_{.value}"))
Join indices and phenology data
final_RS_data <- full_join(
# Indices data (max and min)
final_indices_data,
# Average values of indices per month
monthly_avg_indices %>%
pivot_wider(names_from = index, values_from = c(avg_value_01:auc_mar_oct),
names_glue = "{index}_{.value}")
) %>%
full_join(
# Phenology data
final_phenology_data
) %>%
# Sort cols in alphabetical order
select(PlotObservationID, sort(names(.)[names(.) != "PlotObservationID"]))
Add EUNIS codes
final_RS_data <- final_RS_data %>% left_join(db_Europa_allobs)
data_RS_S2_bands_indices <- data_RS_S2_bands_indices %>%
left_join(db_Europa_allobs)
Monthly spectrophenology per habitat type
# Prepare the data
data_monthly_EUNISa_1 <- data_RS_S2_bands_indices %>%
mutate(month = month(date, label = TRUE, abbr = TRUE, locale="EN-us")) %>%
group_by(month, EUNISa_1, EUNISa_1_descr) %>%
summarise(
mean_NDVI = mean(NDVI, na.rm = TRUE),
sd_NDVI = sd(NDVI, na.rm = TRUE),
n_NDVI = sum(!is.na(NDVI)),
mean_EVI = mean(EVI, na.rm = TRUE),
sd_EVI = sd(EVI, na.rm = TRUE),
n_EVI = sum(!is.na(EVI)),
mean_SAVI = mean(SAVI, na.rm = TRUE),
sd_SAVI = sd(SAVI, na.rm = TRUE),
n_SAVI = sum(!is.na(SAVI)),
.groups = "drop"
)
data_monthly_EUNISa_1 <- data_monthly_EUNISa_1 %>%
# Add label with n
left_join(data_monthly_EUNISa_1 %>%
group_by(EUNISa_1) %>%
summarise(n_total = sum(n_NDVI, na.rm = TRUE)),
by = "EUNISa_1") %>%
mutate(EUNISa_1_label = paste0(EUNISa_1, " (n = ", n_total, ")"))
data_monthly_EUNISa_2 <- data_RS_S2_bands_indices %>%
mutate(month = month(date, label = TRUE, abbr = TRUE, locale="EN-us")) %>%
group_by(month, EUNISa_1, EUNISa_1_descr, EUNISa_2, EUNISa_2_descr) %>%
summarise(
mean_NDVI = mean(NDVI, na.rm = TRUE),
sd_NDVI = sd(NDVI, na.rm = TRUE),
n_NDVI = sum(!is.na(NDVI)),
mean_EVI = mean(EVI, na.rm = TRUE),
sd_EVI = sd(EVI, na.rm = TRUE),
n_EVI = sum(!is.na(EVI)),
mean_SAVI = mean(SAVI, na.rm = TRUE),
sd_SAVI = sd(SAVI, na.rm = TRUE),
n_SAVI = sum(!is.na(SAVI)),
.groups = "drop"
)
data_monthly_EUNISa_2 <- data_monthly_EUNISa_2 %>%
# Add label with n
left_join(data_monthly_EUNISa_2 %>%
group_by(EUNISa_2) %>%
summarise(n_total = sum(n_NDVI, na.rm = TRUE)),
by = "EUNISa_2") %>%
mutate(EUNISa_2_label = paste0(EUNISa_2, " (n = ", n_total, ")"))
EUNIS level 1
# Plots
# EUNISa_1
ggplot(data_monthly_EUNISa_1 %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_1_label, EUNISa_1_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_1, EUNISa_1_descr, sep = " - ")),
aes(x = month, y = mean_NDVI, color = EUNIS,
group = EUNISa_1_label)) +
geom_point() +
geom_line(aes(group = EUNISa_1)) +
#geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI),
#width = 0.2) +
labs(
title = "Monthly NDVI by Habitat Type",
x = "Month",
y = "NDVI",
color = "Habitat (EUNIS1)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS1_NDVI.jpeg"),
dpi = 300, width = 6, height = 2.5)

ggplot(data_monthly_EUNISa_1 %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_1_label, EUNISa_1_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_1, EUNISa_1_descr, sep = " - ")),
aes(x = month, y = mean_EVI, color = EUNIS,
group = EUNISa_1_label)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI),
#width = 0.2) +
labs(
title = "Monthly EVI by Habitat Type",
x = "Month",
y = "EVI",
color = "Habitat (EUNIS1)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS1_EVI.jpeg"),
dpi = 300, width = 6, height = 2.5)

ggplot(data_monthly_EUNISa_1 %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_1_label, EUNISa_1_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_1, EUNISa_1_descr, sep = " - ")),
aes(x = month, y = mean_SAVI, color = EUNIS,
group = EUNISa_1_label)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI),
#width = 0.2) +
labs(
title = "Monthly SAVI by Habitat Type",
x = "Month",
y = "SAVI",
color = "Habitat (EUNIS1)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS1_SAVI.jpeg"),
dpi = 300, width = 6, height = 2.5)

EUNIS level 2
Q
# NDVI
ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "Q" & !is.na(EUNISa_2)) %>%
# Remove those with EUNIS level 2 that does not match current classif
dplyr::filter(EUNISa_2 != "Qa" & EUNISa_2 != "Qb") %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI),
#width = 0.2) +
labs(
title = "Monthly NDVI by Habitat Type",
x = "Month",
y = "NDVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_Q_NDVI.jpeg"),
dpi = 300, width = 7, height = 2.5)

# EVI
ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "Q" & !is.na(EUNISa_2)) %>%
# Remove those with EUNIS level 2 that does not match current classif
dplyr::filter(EUNISa_2 != "Qa" & EUNISa_2 != "Qb") %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI),
#width = 0.2) +
labs(
title = "Monthly EVI by Habitat Type",
x = "Month",
y = "EVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_Q_EVI.jpeg"),
dpi = 300, width = 7, height = 2.5)

# SAVI
ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "Q" & !is.na(EUNISa_2)) %>%
# Remove those with EUNIS level 2 that does not match current classif
dplyr::filter(EUNISa_2 != "Qa" & EUNISa_2 != "Qb") %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI),
#width = 0.2) +
labs(
title = "Monthly SAVI by Habitat Type",
x = "Month",
y = "SAVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_Q_SAVI.jpeg"),
dpi = 300, width = 7, height = 2.5)

R
ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "R" & !is.na(EUNISa_2)) %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI),
#width = 0.2) +
labs(
title = "Monthly NDVI by Habitat Type",
x = "Month",
y = "NDVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_R_NDVI.jpeg"),
dpi = 300, width = 7, height = 2.5)

ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "R" & !is.na(EUNISa_2)) %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI),
#width = 0.2) +
labs(
title = "Monthly EVI by Habitat Type",
x = "Month",
y = "EVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_R_EVI.jpeg"),
dpi = 300, width = 7, height = 2.5)

ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "R" & !is.na(EUNISa_2)) %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI),
#width = 0.2) +
labs(
title = "Monthly SAVI by Habitat Type",
x = "Month",
y = "SAVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_R_SAVI.jpeg"),
dpi = 300, width = 7, height = 2.5)

S
ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "S" & !is.na(EUNISa_2)) %>%
# Remove those with EUNIS level 2 that does not match current classif
dplyr::filter(EUNISa_2 != "Sa" & EUNISa_2 != "Sb") %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI),
#width = 0.2) +
labs(
title = "Monthly NDVI by Habitat Type",
x = "Month",
y = "NDVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_S_NDVI.jpeg"),
dpi = 300, width = 8, height = 2.5)

ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "S" & !is.na(EUNISa_2)) %>%
# Remove those with EUNIS level 2 that does not match current classif
dplyr::filter(EUNISa_2 != "Sa" & EUNISa_2 != "Sb") %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI),
#width = 0.2) +
labs(
title = "Monthly EVI by Habitat Type",
x = "Month",
y = "EVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_S_EVI.jpeg"),
dpi = 300, width = 8, height = 2.5)

ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "S" & !is.na(EUNISa_2)) %>%
# Remove those with EUNIS level 2 that does not match current classif
dplyr::filter(EUNISa_2 != "Sa" & EUNISa_2 != "Sb") %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI),
#width = 0.2) +
labs(
title = "Monthly SAVI by Habitat Type",
x = "Month",
y = "SAVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_S_SAVI.jpeg"),
dpi = 300, width = 8, height = 2.5)

T
ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "T" & !is.na(EUNISa_2)) %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_NDVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_NDVI - sd_NDVI, ymax = mean_NDVI + sd_NDVI),
#width = 0.2) +
labs(
title = "Monthly NDVI by Habitat Type",
x = "Month",
y = "NDVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_T_NDVI.jpeg"),
dpi = 300, width = 6, height = 2.5)

ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "T" & !is.na(EUNISa_2)) %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_EVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_EVI - sd_EVI, ymax = mean_EVI + sd_EVI),
#width = 0.2) +
labs(
title = "Monthly EVI by Habitat Type",
x = "Month",
y = "EVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_T_EVI.jpeg"),
dpi = 300, width = 6, height = 2.5)

ggplot(data_monthly_EUNISa_2 %>%
dplyr::filter(EUNISa_1 == "T" & !is.na(EUNISa_2)) %>%
# If we want to have n points
# mutate(EUNIS = paste(EUNISa_2_label, EUNISa_2_descr, sep = " - ")),
mutate(EUNIS = paste(EUNISa_2, EUNISa_2_descr, sep = " - ")),
aes(x = month, y = mean_SAVI, color = EUNIS, group = EUNIS)) +
geom_point() +
geom_line() +
#geom(aes(ymin = mean_SAVI - sd_SAVI, ymax = mean_SAVI + sd_SAVI),
#width = 0.2) +
labs(
title = "Monthly SAVI by Habitat Type",
x = "Month",
y = "SAVI",
color = "Habitat (EUNIS2)"
) +
theme_minimal()
ggsave(
here("output", "figures", "monthly_spectrophenology", "EUNIS2_T_SAVI.jpeg"),
dpi = 300, width = 6, height = 2.5)

Calculate other phenological metrics
final_RS_data <- final_RS_data %>%
mutate(
# with slope method
# Growing season duration
NDVI_slope_gsd = NDVI_eos_slope_doy - NDVI_sos_slope_doy,
EVI_slope_gsd = NDVI_eos_slope_doy - NDVI_sos_slope_doy,
SAVI_slope_gsd = SAVI_eos_slope_doy - SAVI_sos_slope_doy,
# Difference in value between pos and sos
NDVI_diff_pos_sos_slope_value = NDVI_pos_value - NDVI_sos_slope_value,
EVI_diff_pos_sos_slope_value = EVI_pos_value - EVI_sos_slope_value,
SAVI_diff_pos_sos_slope_value = SAVI_pos_value - SAVI_sos_slope_value,
# Difference in value between pos and eos
NDVI_diff_pos_eos_slope_value = NDVI_pos_value - NDVI_eos_slope_value,
EVI_diff_pos_eos_slope_value = EVI_pos_value - EVI_eos_slope_value,
SAVI_diff_pos_eos_slope_value = SAVI_pos_value - SAVI_eos_slope_value,
# Difference in doy between pos and sos
NDVI_diff_pos_sos_slope_doy = NDVI_pos_doy - NDVI_sos_slope_doy,
EVI_diff_pos_sos_slope_doy = EVI_pos_doy - EVI_sos_slope_doy,
SAVI_diff_pos_sos_slope_doy = SAVI_pos_doy - SAVI_sos_slope_doy,
# Absolute difference in doy between pos and eos
NDVI_diff_pos_eos_slope_doy = abs(NDVI_pos_doy - NDVI_eos_slope_doy),
EVI_diff_pos_eos_slope_doy = abs(EVI_pos_doy - EVI_eos_slope_doy),
SAVI_diff_pos_eos_slope_doy = abs(SAVI_pos_doy - SAVI_eos_slope_doy),
# With threshold method
# Growing season duration
NDVI_threshold_gsd = NDVI_eos_threshold_doy - NDVI_sos_threshold_doy,
EVI_threshold_gsd = NDVI_eos_threshold_doy - NDVI_sos_threshold_doy,
SAVI_threshold_gsd = SAVI_eos_threshold_doy - SAVI_sos_threshold_doy,
# Difference in value between pos and sos
NDVI_diff_pos_sos_threshold_value =
NDVI_pos_value - NDVI_sos_threshold_value,
EVI_diff_pos_sos_threshold_value =
EVI_pos_value - EVI_sos_threshold_value,
SAVI_diff_pos_sos_threshold_value =
SAVI_pos_value - SAVI_sos_threshold_value,
# Difference in value between pos and eos
NDVI_diff_pos_eos_threshold_value =
NDVI_pos_value - NDVI_eos_threshold_value,
EVI_diff_pos_eos_threshold_value =
EVI_pos_value - EVI_eos_threshold_value,
SAVI_diff_pos_eos_threshold_value =
SAVI_pos_value - SAVI_eos_threshold_value,
# Difference in doy between pos and sos
NDVI_diff_pos_sos_threshold_doy = NDVI_pos_doy - NDVI_sos_threshold_doy,
EVI_diff_pos_sos_threshold_doy = EVI_pos_doy - EVI_sos_threshold_doy,
SAVI_diff_pos_sos_threshold_doy = SAVI_pos_doy - SAVI_sos_threshold_doy,
# Absolute difference in doy between pos and eos
NDVI_diff_pos_eos_threshold_doy =
abs(NDVI_pos_doy - NDVI_eos_threshold_doy),
EVI_diff_pos_eos_threshold_doy =
abs(EVI_pos_doy - EVI_eos_threshold_doy),
SAVI_diff_pos_eos_threshold_doy =
abs(SAVI_pos_doy - SAVI_eos_threshold_doy),
# With months method
# Difference in value between pos and march
NDVI_diff_pos_march_value = NDVI_pos_value - NDVI_avg_value_03,
EVI_diff_pos_march_value = EVI_pos_value - EVI_avg_value_03,
SAVI_diff_pos_march_value = SAVI_pos_value - SAVI_avg_value_03,
# Difference in value between pos and oct
NDVI_diff_pos_oct_value = NDVI_pos_value - NDVI_avg_value_10,
EVI_diff_pos_oct_value = EVI_pos_value - EVI_avg_value_10,
SAVI_diff_pos_oct_value = SAVI_pos_value - SAVI_avg_value_10,
# Difference in doy between pos and march
NDVI_diff_pos_march_doy = NDVI_pos_doy - 75,
EVI_diff_pos_march_doy = EVI_pos_doy - 75,
SAVI_diff_pos_march_doy = SAVI_pos_doy - 75,
# Difference in doy between pos and oct
NDVI_diff_pos_oct_doy = abs(NDVI_pos_doy - 285),
EVI_diff_pos_oct_doy = abs(EVI_pos_doy - 285),
SAVI_diff_pos_oct_doy = abs(SAVI_pos_doy - 285)
)
Checks
Slope method
# Growing season duration should be positive
nrow(final_RS_data %>%
dplyr::filter(NDVI_slope_gsd <= 0 | EVI_slope_gsd <= 0 |
SAVI_slope_gsd <= 0))
[1] 0
# Difference in value between pos and sos should be positive
nrow(final_RS_data %>%
dplyr::filter(NDVI_diff_pos_sos_slope_value <= 0))
[1] 108
nrow(final_RS_data %>%
dplyr::filter(EVI_diff_pos_sos_slope_value <= 0))
[1] 91
nrow(final_RS_data %>%
dplyr::filter(SAVI_diff_pos_sos_slope_value <= 0))
[1] 47
# Difference in value between pos and eos should be positive
nrow(final_RS_data %>%
dplyr::filter(NDVI_diff_pos_eos_slope_value <= 0))
[1] 340
nrow(final_RS_data %>%
dplyr::filter(EVI_diff_pos_eos_slope_value <= 0))
[1] 230
nrow(final_RS_data %>%
dplyr::filter(SAVI_diff_pos_eos_slope_value <= 0))
[1] 78
# Difference in doy between pos and sos should be positive
nrow(final_RS_data %>%
dplyr::filter(NDVI_diff_pos_sos_slope_doy <= 0 |
EVI_diff_pos_sos_slope_doy <= 0 |
SAVI_diff_pos_sos_slope_doy <= 0))
[1] 0
# Difference in doy between eos and pos should be positive
nrow(final_RS_data %>%
dplyr::filter(NDVI_diff_pos_eos_slope_doy <= 0 |
EVI_diff_pos_eos_slope_doy <= 0 |
SAVI_diff_pos_eos_slope_doy <= 0))
[1] 0
Threshold method
# Growing season duration should be positive
nrow(final_RS_data %>%
dplyr::filter(NDVI_threshold_gsd <= 0 | EVI_threshold_gsd <= 0 |
SAVI_threshold_gsd <= 0))
[1] 0
# Difference in value between pos and sos should be positive
nrow(final_RS_data %>%
dplyr::filter(NDVI_diff_pos_sos_threshold_value <= 0))
[1] 191
nrow(final_RS_data %>%
dplyr::filter(EVI_diff_pos_sos_threshold_value <= 0))
[1] 188
nrow(final_RS_data %>%
dplyr::filter(SAVI_diff_pos_sos_threshold_value <= 0))
[1] 78
# Difference in value between pos and eos should be positive
nrow(final_RS_data %>%
dplyr::filter(NDVI_diff_pos_eos_threshold_value <= 0))
[1] 218
nrow(final_RS_data %>%
dplyr::filter(EVI_diff_pos_eos_threshold_value <= 0))
[1] 288
nrow(final_RS_data %>%
dplyr::filter(SAVI_diff_pos_eos_threshold_value <= 0))
[1] 79
# Difference in doy between pos and sos should be positive
nrow(final_RS_data %>%
dplyr::filter(NDVI_diff_pos_sos_threshold_doy <= 0 |
EVI_diff_pos_sos_threshold_doy <= 0 |
SAVI_diff_pos_sos_threshold_doy <= 0))
[1] 31
# Difference in doy between eos and pos should be positive
nrow(final_RS_data %>%
dplyr::filter(NDVI_diff_pos_eos_threshold_doy <= 0 |
EVI_diff_pos_eos_threshold_doy <= 0 |
SAVI_diff_pos_eos_threshold_doy <= 0))
[1] 0
HERE: Run. Detect number of peaks in smoothed curves
# Set up parallel plan
plan(multisession, workers = min(parallel::detectCores() - 1))
# Define peak-counting function
count_peaks <- function(df) {
# Convert 1D array column to numeric vector
y <- as.numeric(df$value)
peaks <- findpeaks(y, minpeakdistance = 30, threshold = 0.02)
tibble(
PlotObservationID = unique(df$PlotObservationID),
index = unique(df$index),
num_peaks = if (!is.null(peaks)) nrow(peaks) else 0
)
}
# Apply peak counting in parallel
peak_counts <- GAM_data %>%
filter(fit_type == "iter_3") %>%
arrange(DOY) %>%
group_by(PlotObservationID, index) %>%
nest() %>%
mutate(result = future_map(data, count_peaks, .progress = TRUE,
.options = furrr_options(scheduling = Inf))) %>%
select(-data) %>%
unnest(result)
# Summarize result
peak_counts_summary <- peak_counts %>% count(index, num_peaks)
HERE: save
# # Function to count peaks for each PlotObservationID
# count_peaks <- function(df) {
# y <- df$value
# peaks <- findpeaks(y,
# # Minimum number of indices (e.g., DOY steps)
# # between two peaks
# minpeakdistance = 30,
# # Minimum vertical difference between a peak
# # and its surrounding value
# threshold = 0.02)
# num_peaks <- if (!is.null(peaks)) nrow(peaks) else 0
# return(tibble(PlotObservationID = unique(df$PlotObservationID),
# num_peaks = num_peaks))
# }
#
# # Apply to each group
# peak_counts <- GAM_data %>%
# mutate(value = map_dbl(value, 1)) %>%
# dplyr::filter(fit_type == "iter_3") %>%
# arrange(DOY) %>%
# group_by(PlotObservationID, index) %>%
# group_modify(~ count_peaks(.x)) %>%
# ungroup()
#
# # View result
# peak_counts %>% count(index, num_peaks)
Plot number of peaks
peak_counts %>% count(index, num_peaks) %>%
ggplot(aes(x = index, y = n, fill = factor(num_peaks))) +
geom_bar(stat = "identity", position = position_dodge())
EVI gives less problems, maybe use only this one?
Add number of peaks to data
final_RS_data <- final_RS_data %>%
left_join(peak_counts %>%
pivot_wider(names_from = index, values_from = num_peaks,
names_glue = "{index}_{.value}"))
Plot fit and moments for PlotObservationIDs with zero peaks
Further checks (EVI)
Slope method
# Difference in value between pos and sos should be positive
nrow(final_RS_data %>%
dplyr::filter(EVI_diff_pos_sos_slope_value <= 0))
[1] 91
final_RS_data %>%
dplyr::filter(EVI_diff_pos_sos_slope_value <= 0) %>%
count(EVI_num_peaks)
Error in `count()`:
! Must group by variables found in `.data`.
✖ Column `EVI_num_peaks` is not found.
Run `]8;;x-r-run:rlang::last_trace()rlang::last_trace()]8;;` to see where the error occurred.
Threshold method
Add some columns needed
final_RS_data <- final_RS_data %>%
left_join(
data_RS_S2_bands_indices %>%
distinct(PlotObservationID, year, biogeo, unit, Lctnmth)
)
Add canopy height data
Read the data:
data_RS_CH <- read_csv(
"C:/Data/MOTIVATE/MOTIVATE_RS_data/Canopy_Height_1m/Europe_points_CanopyHeight_1m.csv")
db_Europa <- read_csv(
here("..", "DB_first_check", "data", "clean","db_Europa_20250107.csv")
)
data_RS_CH_ID <- db_Europa %>%
select(PlotObservationID, obs_unique_id) %>%
right_join(data_RS_CH %>%
# Rename to be able to join on this column
rename(obs_unique_id = obs_unique)) %>%
select(PlotObservationID, canopy_height)
Join:
final_RS_data <- final_RS_data %>%
left_join(data_RS_CH_ID %>%
mutate(PlotObservationID = factor(PlotObservationID)))
Save to clean data
write_tsv(final_RS_data,
here("data", "clean","final_RS_data_bands_S2_all_20250804.csv"))
Session info
sessionInfo()
R version 4.5.1 (2025-06-13 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26100)
Matrix products: default
LAPACK version 3.12.1
locale:
[1] LC_COLLATE=Spanish_Spain.utf8 LC_CTYPE=Spanish_Spain.utf8 LC_MONETARY=Spanish_Spain.utf8
[4] LC_NUMERIC=C LC_TIME=Spanish_Spain.utf8
time zone: Europe/Madrid
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] beepr_2.0 pracma_2.4.4 progressr_0.15.1 furrr_0.3.1 future_1.67.0
[6] mgcv_1.9-3 nlme_3.1-168 knitr_1.50 sf_1.0-21 dtplyr_1.3.2
[11] here_1.0.2 lubridate_1.9.4 forcats_1.0.0 stringr_1.5.2 dplyr_1.1.4
[16] purrr_1.1.0 readr_2.1.5 tidyr_1.3.1 tibble_3.3.0 ggplot2_4.0.0
[21] tidyverse_2.0.0 signal_1.8-1
loaded via a namespace (and not attached):
[1] gtable_0.3.6 xfun_0.53 lattice_0.22-7 tzdb_0.5.0
[5] vctrs_0.6.5 tools_4.5.1 generics_0.1.4 parallel_4.5.1
[9] proxy_0.4-27 pkgconfig_2.0.3 Matrix_1.7-4 KernSmooth_2.23-26
[13] data.table_1.17.8 RColorBrewer_1.1-3 S7_0.2.0 lifecycle_1.0.4
[17] compiler_4.5.1 farver_2.1.2 textshaping_1.0.3 codetools_0.2-20
[21] htmltools_0.5.8.1 class_7.3-23 yaml_2.3.10 crayon_1.5.3
[25] pillar_1.11.0 MASS_7.3-65 classInt_0.4-11 parallelly_1.45.1
[29] tidyselect_1.2.1 digest_0.6.37 stringi_1.8.7 listenv_0.9.1
[33] labeling_0.4.3 splines_4.5.1 rprojroot_2.1.1 fastmap_1.2.0
[37] grid_4.5.1 cli_3.6.5 magrittr_2.0.3 utf8_1.2.6
[41] e1071_1.7-16 withr_3.0.2 scales_1.4.0 bit64_4.6.0-1
[45] timechange_0.3.0 rmarkdown_2.29 audio_0.1-11 globals_0.18.0
[49] bit_4.6.0 ragg_1.5.0 hms_1.1.3 evaluate_1.0.5
[53] rlang_1.1.6 Rcpp_1.1.0 glue_1.8.0 DBI_1.2.3
[57] vroom_1.6.5 rstudioapi_0.17.1 R6_2.6.1 systemfonts_1.2.3
[61] units_0.8-7
LS0tDQp0aXRsZTogIlNjcmlwdCB0byB3b3JrIHdpdGggUzIgYmFuZHMgZGVyaXZlZCBmcm9tIEdFRSINCnN1YnRpdGxlOiAiUmVhZCBhbmQgbWFuaXB1bGF0aW9uIGRhdGEsIGNhbGN1bGF0ZSBpbmRpY2VzIg0KYXV0aG9yOiAiQWxpY2lhIFZhbGTDqXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UpDQpgYGANCg0KIyBMb2FkIGxpYnJhcmllcw0KDQpgYGB7cn0NCmxpYnJhcnkoc2lnbmFsKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkoZHRwbHlyKQ0KbGlicmFyeShzZikNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KG1nY3YpDQpsaWJyYXJ5KGZ1dHVyZSkNCmxpYnJhcnkoZnVycnIpDQpsaWJyYXJ5KHByb2dyZXNzcikNCmxpYnJhcnkocHJhY21hKQ0KYGBgDQoNCiMgU2V0IGEgc2ltcGxlIGNvbnNvbGUgcHJvZ3Jlc3MgYmFyDQoNCmBgYHtyfQ0KaGFuZGxlcnMoInR4dHByb2dyZXNzYmFyIikgIyBTaW1wbGUgY29uc29sZSBwcm9ncmVzcyBiYXINCmBgYA0KDQojIFN1cHJlc3MgcGFja2FnZSBtZXNzYWdlcw0KDQpgYGB7cn0NCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7DQogIGxpYnJhcnkobWdjdikNCiAgbGlicmFyeShubG1lKQ0KfSkNCmBgYA0KDQojIExvYWQgcHJldmlvdXNseSBjcmVhdGVkIG9iamVjdHMNCg0KYGBge3J9DQpsb2FkKGZpbGUgPSAib2JqZWN0cy9kYXRhX1JTX1MyX2JhbmRzX2luZGljZXMuUmRhdGEiKQ0KbG9hZChmaWxlID0gIm9iamVjdHMvR0FNX2RhdGFfUzIuUmRhdGEiKQ0KbG9hZChmaWxlID0gIm9iamVjdHMvc21vb3RoZWRfZGF0YV9TMi5SZGF0YSIpDQpsb2FkKGZpbGUgPSAib2JqZWN0cy9tb250aGx5X2F2Z19pbmRpY2VzX1MyLlJkYXRhIikNCmBgYA0KDQojIExvYWQgUmVzdXJ2ZXkgZGINCg0KYGBge3J9DQpkYl9FdXJvcGFfYWxsb2JzIDwtIHJlYWRfY3N2KA0KICBoZXJlKCJkYXRhIiwgImNsZWFuIiwgImRiX0V1cm9wYV9hbGxvYnMuY3N2IikpICU+JQ0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIEVVTklTYV8xLCBFVU5JU2FfMV9kZXNjciwNCiAgICAgICAgIEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjcikgJT4lDQogIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGZhY3RvcihQbG90T2JzZXJ2YXRpb25JRCksDQogICAgICAgICBFVU5JU2FfMSA9IGZhY3RvcihFVU5JU2FfMSksIEVVTklTYV8yID0gZmFjdG9yKEVVTklTYV8yKSkNCmBgYA0KDQojIERlZmluZSBwcmludGFsbCBmdW5jdGlvbg0KDQpgYGB7cn0NCnByaW50YWxsIDwtIGZ1bmN0aW9uKHRpYmJsZSkgew0KICBwcmludCh0aWJibGUsIHdpZHRoID0gSW5mKQ0KICB9DQpgYGANCg0KIyBSZWFkIGZpbGVzIHdpdGggYmFuZCBkYXRhDQoNCkkgZ290IHRoZXNlIGZpbGVzIHVzaW5nIHRoZSBHRUUgY29kZSBwcmVwYXJlZCBieSBCZWEuDQoNClRoZXNlIGZpbGVzIGNvbnRhaW4gYWxsIG9ic2VydmF0aW9ucyBpbiB0aGUgUmVTdXJ2ZXkgZGF0YWJhc2UgZnJvbSAyMDE2IG9ud2FyZC4gSW4gb3JkZXIgdG8gYXZvaWQgY29tcHV0YXRpb24gcHJvYmxlbXMgaW4gR0VFLCBiaW9nZW9ncmFwaGljYWwgdW5pdHMgdGhhdCBjb250YWluIG1vcmUgdGhhbiA0NTAwIHBvaW50cyBoYXZlIGJlZW4gc3ViZGl2aWRlZCBpbiBBcmNHSVMuDQoNCmBgYHtyfQ0KIyBTZXQgdGhlIGZvbGRlciBwYXRoDQpmb2xkZXJfcGF0aCA8LSAiQzovRGF0YS9NT1RJVkFURS9NT1RJVkFURV9SU19kYXRhL1MyL0JhbmRzL2FsbCINCg0KIyBMaXN0IENTViBmaWxlcw0KY3N2X2ZpbGVzIDwtIGxpc3QuZmlsZXMoZm9sZGVyX3BhdGgsIGZ1bGwubmFtZXMgPSBUUlVFLCByZWN1cnNpdmUgPSBUUlVFKQ0KDQojIEZ1bmN0aW9uIHRvIGV4dHJhY3QgYmlvZ2VvIGFuZCB1bml0IGZyb20gdGhlIGZpbGVuYW1lDQpleHRyYWN0X2luZm8gPC0gZnVuY3Rpb24oZmlsZW5hbWUpIHsNCiAgZmlyc3Rfd29yZCA8LSBzdHJzcGxpdChmaWxlbmFtZSwgIl8iKVtbMV1dWzFdDQogIGJpb2dlbyA8LSBzdHJfZXh0cmFjdChmaXJzdF93b3JkLA0KICAgICAgICAgICAgICAgICAgICAgICAgIl4oQUxQfEFOQXxBUkN8QVRMfEJMQUNLU0VBfEJPUnxDT058TUFDQVJPTkVTSUF8TUVEfFBBTk9OSUF8U1RFUFBJQykiKQ0KICB1bml0IDwtIHN0cl9yZW1vdmUoZmlyc3Rfd29yZCwgYmlvZ2VvKQ0KICBpZiAoaXMubmEodW5pdCkgfHwgdW5pdCA9PSAiIikgdW5pdCA8LSBOQV9jaGFyYWN0ZXJfDQogIGxpc3QoYmlvZ2VvID0gYmlvZ2VvLCB1bml0ID0gdW5pdCkNCiAgfQ0KDQoNCiMgRGVmaW5lIGNvbHVtbiB0eXBlczogZm9yY2UgUlNydnlwbCB0byBjaGFyYWN0ZXIsIG90aGVycyBhdXRvLWRldGVjdGVkDQpjdXN0b21fY29sX3R5cGVzIDwtIGNvbHMoDQogIFJTcnZ5cGwgPSBjb2xfY2hhcmFjdGVyKCksDQogIFJTcnZ5c3QgPSBjb2xfY2hhcmFjdGVyKCksDQogIGRlZmF1bHQgPSBjb2xfZ3Vlc3MoKQ0KKQ0KDQojIFJlYWQgYW5kIHByb2Nlc3MgZWFjaCBmaWxlDQpkYXRhX2xpc3QgPC0gbGFwcGx5KGNzdl9maWxlcywgZnVuY3Rpb24oZmlsZSkgew0KICBpbmZvIDwtIGV4dHJhY3RfaW5mbyhiYXNlbmFtZShmaWxlKSkgIyBVc2Ugb25seSB0aGUgZmlsZW5hbWUNCiAgDQogICMgUmVhZCB0aGUgZmlsZQ0KICBkZiA8LSByZWFkX2NzdihmaWxlLCBjb2xfdHlwZXMgPSBjdXN0b21fY29sX3R5cGVzKSAlPiUNCiAgICAjIFJlbW92ZSBjb2x1bW5zIHRoYXQgZ2l2ZSBjb2x1bW4gdHlwZSBwcm9ibGVtcyB3aGVuIGNvbWJpbmluZyBkYXRhDQogICAgc2VsZWN0KC1zdGFydHNfd2l0aCgiRVVOSVMiKSwgLXN0YXJ0c193aXRoKCJSZVN1cnZleSIpKSAlPiUNCiAgICBtdXRhdGUoYmlvZ2VvID0gaW5mbyRiaW9nZW8sIHVuaXQgPSBpbmZvJHVuaXQpDQogIA0KICByZXR1cm4oZGYpDQogIH0pDQoNCiMgQ29tYmluZSBhbGwgZGF0YQ0KZGF0YV9SU19TMl9iYW5kcyA8LSBiaW5kX3Jvd3MoZGF0YV9saXN0KSAlPiUNCiAgcmVuYW1lKFBsb3RPYnNlcnZhdGlvbklEID0gUGx0T2JJRCkNCg0KIyBWaWV3IHRoZSByZXN1bHRpbmcgdGliYmxlDQpwcmludChkYXRhX1JTX1MyX2JhbmRzKQ0KDQojIENvdW50cyBwZXIgYmlvZ2VvIGFuZCB1bml0DQpwcmludChkYXRhX1JTX1MyX2JhbmRzICU+JSBjb3VudChiaW9nZW8sIHVuaXQpLCBuID0gMTAwKQ0KYGBgDQoNCiMgU29tZSBjaGVja3MNCg0KQ2hlY2sgdGhhdCB0aGUgeWVhciBpbiB0aGUgZGF0ZSBvZiB0aGUgaW1hZ2VzIGlzIG5vdCBkaWZmZXJlbnQgdG8gdGhlIHNhbXBsaW5nIHllYXI6DQoNCmBgYHtyfQ0KZGF0YV9SU19TMl9iYW5kcyAlPiUgZHBseXI6OmZpbHRlcih5ZWFyICE9IHllYXIoZGF0ZSkpDQpgYGANCg0KQ2hlY2sgaG93IG1hbnkgZGlmZmVyZW50IGltYWdlcyBhcmUgZm9yIGVhY2ggb2JzZXJ2YXRpb24sIGRhdGUgYW5kIHRpbWU6DQoNCmBgYHtyfQ0KZGF0YV9SU19TMl9iYW5kcyAlPiUgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQsIGRhdGUsIHRpbWVfdXRjKSAlPiUNCiAgc3VtbWFyaXNlKG5faW1hZ2VzID0gbl9kaXN0aW5jdChpbWFnZV9pZCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICBjb3VudChuX2ltYWdlcykNCmBgYA0KDQojIEF2ZXJhZ2UgdGhlIGJhbmRzDQoNCldoZW4gdGhlcmUgaXMgbW9yZSB0aGFuIG9uZSBpbWFnZSBmb3IgZWFjaCBwb2ludCBhbmQgZGF5LCBhdmVyYWdlIHRoZSB2YWx1ZXMgb2YgdGhlIGJhbmRzOg0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBTdW1tYXJpemUgdGhlIGJhbmQgdmFsdWVzIGNvbmRpdGlvbmFsbHkNCmJhbmRfc3VtbWFyeSA8LSBkYXRhX1JTX1MyX2JhbmRzICU+JQ0KICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCwgZGF0ZSkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBuX2ltYWdlcyA9IG5fZGlzdGluY3QoaW1hZ2VfaWQpLA0KICAgIEIxMSA9IGlmIChuX2ltYWdlcyA+IDEpIG1lYW4oQjExLCBuYS5ybSA9IFRSVUUpIGVsc2UgZmlyc3QoQjExKSwNCiAgICBCMiAgPSBpZiAobl9pbWFnZXMgPiAxKSBtZWFuKEIyLCAgbmEucm0gPSBUUlVFKSBlbHNlIGZpcnN0KEIyKSwNCiAgICBCMyAgPSBpZiAobl9pbWFnZXMgPiAxKSBtZWFuKEIzLCAgbmEucm0gPSBUUlVFKSBlbHNlIGZpcnN0KEIzKSwNCiAgICBCNCAgPSBpZiAobl9pbWFnZXMgPiAxKSBtZWFuKEI0LCAgbmEucm0gPSBUUlVFKSBlbHNlIGZpcnN0KEI0KSwNCiAgICBCOCAgPSBpZiAobl9pbWFnZXMgPiAxKSBtZWFuKEI4LCAgbmEucm0gPSBUUlVFKSBlbHNlIGZpcnN0KEI4KSwNCiAgICAuZ3JvdXBzID0gImRyb3AiDQogICkgDQoNCiMgQ2FsY3VsYXRlIGhvdyBtYW55IGRpZmZlcmVudCBkYXlzIGZvciBlYWNoIFBsb3RPYnNlcnZhdGlvbklEDQpuX2RheXMgPC0gYmFuZF9zdW1tYXJ5ICU+JQ0KICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCkgJT4lDQogIHN1bW1hcmlzZShuX2RheXMgPSBuX2Rpc3RpbmN0KGRhdGUpKQ0KDQojIEpvaW4gYmFjayB0byBvcmlnaW5hbCBkYXRhDQpkYXRhX1JTX1MyX2JhbmRzX3VwZGF0ZWQgPC0gZGF0YV9SU19TMl9iYW5kcyAlPiUNCiAgIyBSZW1vdmUgb2xkIGJhbmQgdmFsdWVzDQogIHNlbGVjdCgtQjExLCAtQjIsIC1CMywgLUI0LCAtQjgpICU+JQ0KICAjIEpvaW4gYmFuZF9zdW1tYXJ5DQogIGxlZnRfam9pbihiYW5kX3N1bW1hcnksIGJ5ID0gYygiUGxvdE9ic2VydmF0aW9uSUQiLCAiZGF0ZSIpKSAlPiUNCiAgIyBLZWVwIG9uZSByb3cgcGVyIGdyb3VwDQogIGRpc3RpbmN0KFBsb3RPYnNlcnZhdGlvbklELCBkYXRlLCAua2VlcF9hbGwgPSBUUlVFKSAlPiUNCiAgIyBSZW1vdmUgdW53YW50ZWQgY29sdW1ucw0KICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtaW1hZ2VfaWQsIC0uZ2VvLCAtdGltZV91dGMsIC10aW1lc3RhbXApICU+JQ0KICAjIEpvaW4NCiAgbGVmdF9qb2luKG5fZGF5cykNCmBgYA0KDQojIENhbGN1bGF0ZSBpbmRpY2VzDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQojIENhbGN1bGF0ZSBpbmRpY2VzDQpkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMgPC0gZGF0YV9SU19TMl9iYW5kc191cGRhdGVkICU+JQ0KICAjIFNldCBQbG90T2JzZXJ2YXRpb25JRCBhcyBmYWN0b3INCiAgbXV0YXRlKFBsb3RPYnNlcnZhdGlvbklEID0gZmFjdG9yKFBsb3RPYnNlcnZhdGlvbklEKSkgJT4lDQogICMgUmVuYW1lIHRoZSBiYW5kcw0KICByZW5hbWUoYmx1ZSA9IEIyLCBncmVlbiA9IEIzLCByZWQgPSBCNCwgTklSID0gQjgsIFNXSVIgPSBCMTEpICU+JQ0KICAjIFNjYWxlIHRoZSBiYW5kcw0KICBtdXRhdGUoYmx1ZSA9IGJsdWUgLyAxMDAwMCwgZ3JlZW4gPSBncmVlbiAvIDEwMDAwLCByZWQgPSByZWQgLyAxMDAwMCwNCiAgICAgICAgIE5JUiA9IE5JUiAvIDEwMDAwLCBTV0lSID0gU1dJUiAvIDEwMDAwKSAlPiUNCiAgIyBDcmVhdGUgY29sdW1uIHRoYXQgY29tYmluZXMgdGhlIGRheSBvZiB0aGUgbW9udGggYW5kIHRoZSB0aW1lDQogIG11dGF0ZSgNCiAgICBkYXRlID0gYXMuUE9TSVhjdChkYXRlKSwNCiAgICAjIE5vcm1hbGl6ZSB0aGUgZGF0ZXMgdG8gYSBmaXhlZCB5ZWFyICgyMDAwKQ0KICAgICMgc28gdGhhdCBzZWFzb25hbCBwYXR0ZXJucyBhY3Jvc3MgZGlmZmVyZW50IHllYXJzIGNhbiBiZSBjb21wYXJlZCB2aXN1YWxseQ0KICAgIGRheV9tb250aCA9IGFzLlBPU0lYY3QoZm9ybWF0KGRhdGUsICIyMDAwLSVtLSVkIikpKSAlPiUNCiAgIyBDcmVhdGUgY29sdW1uIHdpdGggRE9ZDQogIG11dGF0ZShET1kgPSB5ZGF5KGRhdGUpKSAlPiUNCiAgIyBDYWxjdWxhdGUgTkRWSQ0KICBtdXRhdGUoTkRWSSA9IChOSVIgLSByZWQpIC8gKE5JUiArIHJlZCksDQogICAgICAgICBFVkkgPSAoTklSIC0gcmVkKSAqIDIuNSAvIChOSVIgKyA2ICogcmVkIC0gNy41ICogYmx1ZSArIDEpLA0KICAgICAgICAgU0FWSSA9IChOSVIgLSByZWQpICogMS41IC8gKE5JUiArIHJlZCArIDAuNSksDQogICAgICAgICBORE1JID0gKE5JUiAtIFNXSVIpIC8gKE5JUiArIFNXSVIpLA0KICAgICAgICAgTkRXSSA9IChncmVlbiAtIE5JUikgLyAoZ3JlZW4gKyBOSVIpKSAlPiUNCiAgIyBTZXR0aW5nIHZhbHVlcyBvZiBpbmRpY2VzIG91dHNpZGUgZXhwZWN0ZWQgcmFuZ2VzIChlcnJvcnMpIHRvIE5BDQogIG11dGF0ZShFVkkgPSBpZl9lbHNlKEVWSSA+IDEgfCBFVkkgPCAtMSwgTkEsIEVWSSkpICMgMTIxNjYgdmFsdWVzIG9mIEVWSSBhcyBOQQ0KYGBgDQoNClNhdmU6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpzYXZlKGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcywgZmlsZSA9ICJvYmplY3RzL2RhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcy5SZGF0YSIpDQpgYGANCg0KUGxvdCBuX2RheXRpbWU6DQoNCmBgYHtyfQ0KZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCkgJT4lDQogIHN1bW1hcmlzZShuX2RheXMgPSBmaXJzdChuX2RheXMpKSAlPiUgdW5ncm91cCgpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBuX2RheXMpKSArIGdlb21faGlzdG9ncmFtKGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJ3aGl0ZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIyBDb21wdXRlIHBoZW5vbG9naWNhbCBtZXRyaWNzIGZyb20gbW9kZWxzIGZpdHRlZCB0byB0aW1lIHNlcmllcyBkYXRhDQoNCiMjIEZ1bmN0aW9uDQoNCiMjIEhFUkUgKGFscmVhZHkgZG9uZSBpbiBMYW5kc2F0KTogTW9kaWZ5IGZ1bmN0aW9uIGFuZCByZXJ1biBldmVyeXRoaW5nIGZyb20gaGVyZSAoVEJEKQ0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBNb2RpZnkgdGhlc2UgcGFydHMgaW4gdGhlIGZ1bmN0aW9uIGJlbG93IHRvIGVuc3VyZSB0aGF0DQojIHNvc190aHJlc2hvbGQgPCBwb3MgYW5kIGVvc190aHJlc2hvbGQgPiBwb3MNCnNvc190aHJlc2hvbGQgPC0gaWYgKCFpcy5uYSh1X3NvcykgJiYgIWlzLm5hKHBvcykpIHsNCiAgY2FuZGlkYXRlcyA8LSB4W3doaWNoKHByZWQgPj0gdV9zb3MgJiB4IDw9IHBvcyldDQogIGlmIChsZW5ndGgoY2FuZGlkYXRlcykgPiAwKSBjYW5kaWRhdGVzWzFdIGVsc2UgTkFfcmVhbF8NCn0gZWxzZSBOQV9yZWFsXw0KDQplb3NfdGhyZXNob2xkIDwtIGlmICghaXMubmEodV9lb3MpKSB7DQogIHhbcmV2KHdoaWNoKHggPiBwb3MgJiBwcmVkID49IHVfZW9zKSlbMV1dDQp9IGVsc2UgTkFfcmVhbF8NCmBgYA0KDQpVc2luZyBHQU1zLCByZXdlaWdodGluZyBhbmQgMyBpdGVyYXRpb25zLg0KDQpVc2luZyBib3RoIGEgY2hhbmdlIGRldGVjdGlvbiBtZXRob2QgKG1heGltdW0gc2xvcGUpIGFuZCBhIHRocmVzaG9sZCBtZXRob2QgKDUwJSBhbXBsaXR1ZGUpIHRvIGNhbGN1bGF0ZSBzb3MgYW5kIGVvcy4NCg0KQXBwcm9hY2ggc2ltaWxhciB0byBodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLmphZy4yMDIwLjEwMjE3MiBmb3IgR0FNIGZpdHRpbmcgYW5kIGNoYW5nZSBkZXRlY3Rpb24gbWV0aG9kLCBhbmQgdG8gaHR0cHM6Ly93d3cubWRwaS5jb20vMjA3Mi00MjkyLzEyLzIyLzM3MzggZm90IHRocmVzaG9sZCBtZXRob2QuDQoNCkRlZmluZSBmdW5jdGlvbiB0byBjb21wdXRlIHBoZW5vbG9neSBtZXRyaWNzIHVzaW5nIEdBTSBmaXQgYW5kIE5EVkkgLyBFVkkgLyBTQVZJOg0KDQpgYGB7cn0NCmNvbXB1dGVfbWV0cmljc19tb2RlbHMgPC0gZnVuY3Rpb24oZGYsIGluZGV4X2NvbHMgPSBjKCJORFZJIiwgIkVWSSIsICJTQVZJIikpIHsNCiAgc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsNCiAgICBsaWJyYXJ5KG1nY3YpDQogICAgbGlicmFyeShubG1lKQ0KICAgIH0pDQogIA0KICBwbGFuKG11bHRpc2Vzc2lvbikgICMgU2V0IHVwIHBhcmFsbGVsIHByb2Nlc3NpbmcNCiAgDQogICMgQ3JlYXRlIGEgbGlzdCBvZiBpbmRleC1zcGVjaWZpYyBkYXRhIGZyYW1lcw0KICBpbmRleF9kZnMgPC0gbGFwcGx5KGluZGV4X2NvbHMsIGZ1bmN0aW9uKGluZGV4X2NvbCkgew0KICAgIGxpc3QoaW5kZXhfY29sID0gaW5kZXhfY29sLCBkZiA9IGRmICU+JQ0KICAgICAgICAgICBzZWxlY3QoRE9ZLCBQbG90T2JzZXJ2YXRpb25JRCwgYWxsX29mKGluZGV4X2NvbCkpKQ0KICAgIH0pDQogIA0KICAjIERlZmluZSB0aGUgcHJvY2Vzc2luZyBmdW5jdGlvbiBmb3IgZWFjaCBpbmRleA0KICBwcm9jZXNzX2luZGV4IDwtIGZ1bmN0aW9uKGluZGV4X2RhdGEpIHsNCiAgICBpbmRleF9jb2wgPC0gaW5kZXhfZGF0YSRpbmRleF9jb2wNCiAgICBkZl9pbmRleCA8LSBpbmRleF9kYXRhJGRmICU+JQ0KICAgICAgZmlsdGVyKCFpcy5uYSguZGF0YVtbaW5kZXhfY29sXV0pKSAlPiUNCiAgICAgIGFycmFuZ2UoRE9ZKQ0KICAgIA0KICAgIHBsb3RfaWQgPC0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKQ0KDQogICAgaWYgKG5yb3coZGZfaW5kZXgpIDwgMTApIHsNCiAgICAgIG1lc3NhZ2UoIiAgU2tpcHBlZDogaW5zdWZmaWNpZW50IGRhdGEgKDwgMTAgcm93cykiKQ0KICAgICAgcmV0dXJuKHRpYmJsZShQbG90T2JzZXJ2YXRpb25JRCA9IHBsb3RfaWQsIGluZGV4ID0gaW5kZXhfY29sLA0KICAgICAgICAgICAgICAgICAgICBzb3Nfc2xvcGUgPSBOQV9yZWFsXywgc29zX3RocmVzaG9sZCA9IE5BX3JlYWxfLA0KICAgICAgICAgICAgICAgICAgICBwb3MgPSBOQV9yZWFsXywgZW9zX3Nsb3BlID0gTkFfcmVhbF8sIA0KICAgICAgICAgICAgICAgICAgICBlb3NfdGhyZXNob2xkID0gTkFfcmVhbF8sIGF1Y19zbG9wZSA9IE5BX3JlYWxfLA0KICAgICAgICAgICAgICAgICAgICBhdWNfdGhyZXNob2xkID0gTkFfcmVhbF8sIFZtYXggPSBOQV9yZWFsXywNCiAgICAgICAgICAgICAgICAgICAgRE9ZID0gZGZfaW5kZXgkRE9ZLCB2YWx1ZSA9IE5BX3JlYWxfKSkNCiAgICB9DQogICAgDQogICAgIyBSZXBsYWNlIGVhcmx5L2xhdGUgRE9ZIHZhbHVlcw0KICAgIGJhc2VfdmFsdWVfZWFybHkgPC0gbWVhbihkZl9pbmRleCAlPiUgZmlsdGVyKERPWSA8PSA1MCkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB1bGwoaW5kZXhfY29sKSwgbmEucm0gPSBUUlVFKQ0KICAgIGJhc2VfdmFsdWVfbGF0ZSAgPC0gbWVhbihkZl9pbmRleCAlPiUgZmlsdGVyKERPWSA+PSAzMTUpICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdWxsKGluZGV4X2NvbCksIG5hLnJtID0gVFJVRSkNCg0KICAgIGRmX2luZGV4IDwtIGRmX2luZGV4ICU+JQ0KICAgICAgbXV0YXRlKCEhaW5kZXhfY29sIDo9IGNhc2Vfd2hlbigNCiAgICAgICAgRE9ZIDw9IDUwIH4gYmFzZV92YWx1ZV9lYXJseSwNCiAgICAgICAgRE9ZID49IDMxNSB+IGJhc2VfdmFsdWVfbGF0ZSwNCiAgICAgICAgVFJVRSB+IC5kYXRhW1tpbmRleF9jb2xdXQ0KICAgICAgKSkNCg0KICAgIHggPC0gZGZfaW5kZXgkRE9ZDQogICAgeSA8LSBkZl9pbmRleFtbaW5kZXhfY29sXV0NCiAgICB3ZWlnaHRzIDwtIHJlcCgxLCBsZW5ndGgoeSkpDQogICAgDQogICAgIyBHQU0gZml0DQogICAgcHJlZCA8LSBOVUxMDQogICAgZm9yIChpIGluIDE6Mykgew0KICAgICAgZ2FtX2ZpdCA8LSB0cnlDYXRjaCh7DQogICAgICAgIG1nY3Y6OmJhbSh5IH4gcyh4LCBicyA9ICJ0cCIpLHdlaWdodHMgPSB3ZWlnaHRzKQ0KICAgICAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICAgICAgICBtZXNzYWdlKCIgIEdBTSBmaXR0aW5nIGZhaWxlZCBmb3IgIiwgcGxvdF9pZCwgIiAtICIsIGluZGV4X2NvbCwgIjogIiwgDQogICAgICAgICAgICAgICAgICBlJG1lc3NhZ2UpDQogICAgICAgICAgcmV0dXJuKE5VTEwpDQogICAgICAgICAgfSkNCiAgICAgIGlmIChpcy5udWxsKGdhbV9maXQpKSB7DQogICAgICAgIHJldHVybih0aWJibGUoDQogICAgICAgICAgUGxvdE9ic2VydmF0aW9uSUQgPSBwbG90X2lkLA0KICAgICAgICAgIGluZGV4ID0gaW5kZXhfY29sLA0KICAgICAgICAgIHNvc19zbG9wZSA9IE5BX3JlYWxfLA0KICAgICAgICAgIHNvc190aHJlc2hvbGQgPSBOQV9yZWFsXywNCiAgICAgICAgICBwb3MgPSBOQV9yZWFsXywNCiAgICAgICAgICBlb3Nfc2xvcGUgPSBOQV9yZWFsXywgDQogICAgICAgICAgZW9zX3RocmVzaG9sZCA9IE5BX3JlYWxfLCANCiAgICAgICAgICBhdWNfc2xvcGUgPSBOQV9yZWFsXywNCiAgICAgICAgICBhdWNfdGhyZXNob2xkID0gTkFfcmVhbF8sIA0KICAgICAgICAgIFZtaW5fcHJlID0gTkFfcmVhbF8sIA0KICAgICAgICAgIFZtaW5fcG9zdCA9IE5BX3JlYWxfLA0KICAgICAgICAgIFZtYXggPSBOQV9yZWFsXywgDQogICAgICAgICAgdV9zb3MgPSBOQV9yZWFsXywgDQogICAgICAgICAgdV9lb3MgPSBOQV9yZWFsXywNCiAgICAgICAgICBET1kgPSBkZl9pbmRleCRET1ksDQogICAgICAgICAgdmFsdWUgPSBOQV9yZWFsXykpDQogICAgICAgIH0NCiAgICAgIA0KICAgICAgcHJlZCA8LSB0cnlDYXRjaCh7DQogICAgICAgIHByZWRpY3QoZ2FtX2ZpdCwgbmV3ZGF0YSA9IHRpYmJsZSh4ID0geCkpDQogICAgICAgIH0sIGVycm9yID0gZnVuY3Rpb24oZSkgew0KICAgICAgICAgIG1lc3NhZ2UoIlByZWRpY3Rpb24gZmFpbGVkIGZvciAiLCBwbG90X2lkLCAiIC0gIiwgaW5kZXhfY29sLCAiOiAiLA0KICAgICAgICAgICAgICAgICAgZSRtZXNzYWdlKQ0KICAgICAgICAgIHJldHVybihyZXAoTkFfcmVhbF8sIGxlbmd0aCh4KSkpDQogICAgICAgICAgfSkNCiAgICAgIA0KICAgICAgaWR4X2JldHdlZW4gPC0gd2hpY2goeCA+IDUwICYgeCA8IDMxNSAmICFpcy5uYShwcmVkKSAmIHByZWQgIT0gMCkNCiAgICAgIHdlaWdodHMgPC0gcmVwKDEsIGxlbmd0aCh5KSkNCiAgICAgIHdlaWdodHNbaWR4X2JldHdlZW5dIDwtICh5W2lkeF9iZXR3ZWVuXSAvIChwcmVkW2lkeF9iZXR3ZWVuXSArIDFlLTYpKV40DQogICAgICB3ZWlnaHRzW3dlaWdodHMgPiAxIHwgaXMubmEod2VpZ2h0cyldIDwtIDENCiAgICAgIH0NCiAgICANCiAgICAjIENvbXB1dGUgbWV0cmljcw0KICAgIHNsb3BlIDwtIGMoTkEsIGRpZmYocHJlZCkpDQogICAgaWR4IDwtIHdoaWNoKHggPj0gNTAgJiB4IDw9IDMxNSkNCiAgICBwb3MgPC0gaWYgKGxlbmd0aChpZHgpID4gMCkgeFtpZHhdW3doaWNoLm1heChwcmVkW2lkeF0pXSBlbHNlIE5BX3JlYWxfDQoNCiAgICBzb3Nfc2xvcGUgPC0gaWYgKCFpcy5uYShwb3MpKSB7DQogICAgICBpZHggPC0gd2hpY2goeCA8IHBvcykNCiAgICAgIGlmIChsZW5ndGgoaWR4KSA+IDApIHhbaWR4XVt3aGljaC5tYXgoc2xvcGVbaWR4XSldIGVsc2UgTkFfcmVhbF8NCiAgICB9IGVsc2UgTkFfcmVhbF8NCg0KICAgIGVvc19zbG9wZSA8LSBpZiAoIWlzLm5hKHBvcykpIHsNCiAgICAgIGlkeCA8LSB3aGljaCh4ID4gcG9zKQ0KICAgICAgaWYgKGxlbmd0aChpZHgpID4gMCkgeFtpZHhdW3doaWNoLm1pbihzbG9wZVtpZHhdKV0gZWxzZSBOQV9yZWFsXw0KICAgIH0gZWxzZSBOQV9yZWFsXw0KDQogICAgaW50ZWdyYXRpb25faWR4X3Nsb3BlIDwtIHdoaWNoKHggPj0gc29zX3Nsb3BlICYgeCA8PSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlb3Nfc2xvcGUgJiAhaXMubmEocHJlZCkpDQogICAgYXVjX3Nsb3BlIDwtIGlmIChsZW5ndGgoaW50ZWdyYXRpb25faWR4X3Nsb3BlKSA+IDEpIHsNCiAgICAgIHN1bShkaWZmKHhbaW50ZWdyYXRpb25faWR4X3Nsb3BlXSkgKiANCiAgICAgICAgICAgIHpvbzo6cm9sbG1lYW4ocHJlZFtpbnRlZ3JhdGlvbl9pZHhfc2xvcGVdLCAyKSkNCiAgICAgIH0gZWxzZSBOQV9yZWFsXw0KICAgIA0KICAgICMgVm1pbiBhbnRlcyB5IGRlc3B1w6lzIGRlbCBwaWNvDQogICAgVm1pbl9wcmUgPC0gaWYgKCFpcy5uYShwb3MpKSBtaW4ocHJlZFt4IDw9IHBvc10sIG5hLnJtID0gVFJVRSllbHNlIE5BX3JlYWxfDQogICAgVm1pbl9wb3N0IDwtIGlmICghaXMubmEocG9zKSkgbWluKHByZWRbeCA+PSBwb3NdLCBuYS5ybSA9IFRSVUUpIGVsc2UgTkFfcmVhbF8NCiAgICBWbWF4IDwtIG1heChwcmVkLCBuYS5ybSA9IFRSVUUpDQogICAgDQogICAgIyBVbWJyYWxlcyByZWxhdGl2b3MNCiAgICBwIDwtIDAuNQ0KICAgIHVfc29zIDwtIGlmICghaXMubmEoVm1pbl9wcmUpKSBWbWluX3ByZSArIHAgKiAoVm1heCAtIFZtaW5fcHJlKSBlbHNlIE5BX3JlYWxfDQogICAgdV9lb3MgPC0gaWYgKCFpcy5uYShWbWluX3Bvc3QpKSBWbWluX3Bvc3QgKyBwICogKFZtYXggLSBWbWluX3Bvc3QpIGVsc2UgTkFfcmVhbF8NCiAgICANCiAgICAjIERPWSBkb25kZSBzZSBjcnV6YW4gbG9zIHVtYnJhbGVzDQogICAgc29zX3RocmVzaG9sZCA8LSBpZiAoIWlzLm5hKHVfc29zKSkgeFt3aGljaChwcmVkID49IHVfc29zKVsxXV0gZWxzZSBOQV9yZWFsXw0KICAgIGVvc190aHJlc2hvbGQgPC0gaWYgKCFpcy5uYSh1X2VvcykpIHhbcmV2KHdoaWNoKHByZWQgPj0gdV9lb3MpKVsxXV0gZWxzZSBOQV9yZWFsXw0KICAgIA0KICAgIGludGVncmF0aW9uX2lkeF90aHJlc2hvbGQgPC0gd2hpY2goeCA+PSBzb3NfdGhyZXNob2xkICYgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPD0gZW9zX3RocmVzaG9sZCAmICFpcy5uYShwcmVkKSkNCiAgICBhdWNfdGhyZXNob2xkIDwtIGlmIChsZW5ndGgoaW50ZWdyYXRpb25faWR4X3RocmVzaG9sZCkgPiAxKSB7DQogICAgICBzdW0oZGlmZih4W2ludGVncmF0aW9uX2lkeF90aHJlc2hvbGRdKSAqIA0KICAgICAgICAgICAgem9vOjpyb2xsbWVhbihwcmVkW2ludGVncmF0aW9uX2lkeF90aHJlc2hvbGRdLCAyKSkNCiAgICAgIH0gZWxzZSBOQV9yZWFsXw0KICAgIA0KICAgICMgMS4gUHJlZGljY2lvbmVzIHBvciBET1kNCiAgICBmaXRzX2RmIDwtIHRpYmJsZSgNCiAgICAgIFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICAgIERPWSA9IHgsDQogICAgICB2YWx1ZSA9IHByZWQsDQogICAgICBpbmRleCA9IGluZGV4X2NvbA0KICAgICAgKQ0KICAgIA0KICAgICMgMi4gTcOpdHJpY2FzIHJlc3VtZW4NCiAgICBtZXRyaWNzX2RmIDwtIHRpYmJsZSgNCiAgICAgIFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmX2luZGV4JFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICAgIGluZGV4ID0gaW5kZXhfY29sLA0KICAgICAgc29zX3Nsb3BlID0gc29zX3Nsb3BlLA0KICAgICAgc29zX3RocmVzaG9sZCA9IHNvc190aHJlc2hvbGQsDQogICAgICBwb3MgPSBwb3MsDQogICAgICBlb3Nfc2xvcGUgPSBlb3Nfc2xvcGUsDQogICAgICBlb3NfdGhyZXNob2xkID0gZW9zX3RocmVzaG9sZCwNCiAgICAgIGF1Y19zbG9wZSA9IGF1Y19zbG9wZSwNCiAgICAgIGF1Y190aHJlc2hvbGQgPSBhdWNfdGhyZXNob2xkLA0KICAgICAgVm1pbl9wcmUgPSBWbWluX3ByZSwNCiAgICAgIFZtaW5fcG9zdCA9IFZtaW5fcG9zdCwNCiAgICAgIFZtYXggPSBWbWF4LA0KICAgICAgdV9zb3MgPSB1X3NvcywNCiAgICAgIHVfZW9zID0gdV9lb3MNCiAgICAgICkNCiAgICANCiAgICAjIDMuIFVuaXIgcG9yIFBsb3RPYnNlcnZhdGlvbklELCBpbmRleA0KICAgIGZpbmFsX2RmIDwtIGxlZnRfam9pbihmaXRzX2RmLCBtZXRyaWNzX2RmLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJQbG90T2JzZXJ2YXRpb25JRCIsICJpbmRleCIpKQ0KICB9DQogIA0KICAjIFJ1biBpbiBwYXJhbGxlbA0KICByZXN1bHRzIDwtIGZ1dHVyZV9tYXAoaW5kZXhfZGZzLCBwcm9jZXNzX2luZGV4LCAucHJvZ3Jlc3MgPSBUUlVFKQ0KICByZXN1bHRzIDwtIHB1cnJyOjpjb21wYWN0KHJlc3VsdHMpICAjIHJlbW92ZXMgTlVMTHMNCiAgaWYgKGxlbmd0aChyZXN1bHRzKSA9PSAwKSByZXR1cm4odGliYmxlKCkpICAjIG9yIHJldHVybihOVUxMKQ0KICBiaW5kX3Jvd3MocmVzdWx0cykNCn0NCmBgYA0KDQojIyBDYWxjdWxhdGlvbg0KDQpBcHBseSB0aGUgZnVuY3Rpb24gd2l0aCBiYXRjaCBwcm9jZXNzaW5nDQoNCmBgYHtyIGV2YWw9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpwbGFuKG11bHRpc2Vzc2lvbiwgd29ya2VycyA9IGF2YWlsYWJsZUNvcmVzKCkgLSAxKQ0KDQppZHMgPC0gdW5pcXVlKGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyRQbG90T2JzZXJ2YXRpb25JRCkNCmJhdGNoZXMgPC0gc3BsaXQoaWRzLCBjZWlsaW5nKHNlcV9hbG9uZyhpZHMpIC8gNTApKSAgIyBiYXRjaGVzIG9mIDUwDQoNCnN0YXJ0X3RvdGFsIDwtIFN5cy50aW1lKCkNCg0KR0FNX2RhdGEgPC0gbWFwX2RmcihzZXFfYWxvbmcoYmF0Y2hlcyksIGZ1bmN0aW9uKGkpIHsNCiAgYmF0Y2hfaWRzIDwtIGJhdGNoZXNbW2ldXQ0KICB0b3RhbF9iYXRjaGVzIDwtIGxlbmd0aChiYXRjaGVzKQ0KICBiYXRjaF9maWxlIDwtIGZpbGUucGF0aCgib2JqZWN0cy9HQU1fYmF0Y2hlc19TMiIsIHBhc3RlMCgiYmF0Y2hfIiwgaSwgIi5yZHMiKSkNCg0KICBpZiAoZmlsZS5leGlzdHMoYmF0Y2hfZmlsZSkpIHsNCiAgICBtZXNzYWdlKCLinIUgQmF0Y2ggICIsIGksICIgb2YgIiwgdG90YWxfYmF0Y2hlcywgDQogICAgICAgICAgICAiIGFscmVhZHkgcHJvY2Vzc2VkLiBMb2FkaW5nIGZyb20gZmlsZS4iKQ0KICAgIHJldHVybihyZWFkUkRTKGJhdGNoX2ZpbGUpKQ0KICB9DQoNCiAgbWVzc2FnZSgi8J+UhCBQcm9jZXNzaW5nIGJhdGNoICAiLCBpLCAiIG9mICIsIHRvdGFsX2JhdGNoZXMsICIgd2l0aCAiLA0KICAgICAgICAgIGxlbmd0aChiYXRjaF9pZHMpLCAiIElEcy4uLiIpDQoNCiAgc3RhcnRfYmF0Y2ggPC0gU3lzLnRpbWUoKQ0KDQogIHJlc3VsdCA8LSBkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMgJT4lDQogICAgZmlsdGVyKFBsb3RPYnNlcnZhdGlvbklEICVpbiUgYmF0Y2hfaWRzKSAlPiUNCiAgICBncm91cF9zcGxpdChQbG90T2JzZXJ2YXRpb25JRCkgJT4lDQogICAgc2V0X25hbWVzKG1hcF9jaHIoLiwgfiBhcy5jaGFyYWN0ZXIodW5pcXVlKC54JFBsb3RPYnNlcnZhdGlvbklEKSkpKSAlPiUNCiAgICBmdXR1cmVfbWFwX2Rmcih+IGNvbXB1dGVfbWV0cmljc19tb2RlbHMoZGYgPSAuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleF9jb2xzID0gYygiTkRWSSIsICJFVkkiLCAiU0FWSSIpKSwNCiAgICAgICAgICAgICAgICAgICAucHJvZ3Jlc3MgPSBUUlVFKQ0KDQogIGVuZF9iYXRjaCA8LSBTeXMudGltZSgpDQogIGR1cmF0aW9uIDwtIHJvdW5kKGRpZmZ0aW1lKGVuZF9iYXRjaCwgc3RhcnRfYmF0Y2gsIHVuaXRzID0gIm1pbnMiKSwgMikNCiAgbWVzc2FnZSgi4o+x77iPIEJhdGNoIHRpbWUgIiwgaSwgIjogIiwgZHVyYXRpb24sICIgbWludXRlcyIpDQoNCiAgbWVzc2FnZSgi8J+SviBTYXZpbmcgYmF0Y2ggIiwgaSwgIiB0byBmaWxlLi4uIikNCiAgc2F2ZVJEUyhyZXN1bHQsIGJhdGNoX2ZpbGUpDQogIG1lc3NhZ2UoIuKchSBCYXRjaCAiLCBpLCAiIHNhdmVkLiIpIA0KDQogIHJlc3VsdA0KfSkNCg0KZW5kX3RvdGFsIDwtIFN5cy50aW1lKCkNCnRvdGFsX3RpbWUgPC0gcm91bmQoZGlmZnRpbWUoZW5kX3RvdGFsLCBzdGFydF90b3RhbCwgdW5pdHMgPSAibWlucyIpLCAyKQ0KbWVzc2FnZSgi4o+x77iPIFRvdGFsIHRpbWU6ICIsIHRvdGFsX3RpbWUsICIgbWludXRlcyIpDQpgYGANCg0KYGBge3J9DQpwbGFuKHNlcXVlbnRpYWwpDQpgYGANCg0KIyMgU2F2ZQ0KDQpMb29rOg0KDQpgYGB7cn0NCkdBTV9kYXRhDQpgYGANCg0KU2F2ZSBhcyBhbiBvYmplY3Q6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpzYXZlKEdBTV9kYXRhLCBmaWxlID0gIm9iamVjdHMvR0FNX2RhdGFfUzIuUmRhdGEiKQ0KYGBgDQoNCiMjIEV4dHJhY3QgYXZlcmFnZSB2YWx1ZXMgb2YgaW5kaWNlcyBwZXIgbW9udGgNCg0KRXh0cmFjdCBhdmVyYWdlIHZhbHVlcyBvZiBpbmRpY2VzIHBlciBtb250aCBhbmQgQVVDIGJldHdlZW4gTWFyY2ggYW5kIE9jdG9iZXIgDQoNCmBgYHtyfQ0KZXh0cmFjdF9tb250aGx5X2F2Z19pbmRpY2VzIDwtIGZ1bmN0aW9uKA0KICBHQU1fZGF0YSwgDQogIG1vbnRobHlfZG95cyA9IGxpc3QoIjAxIiA9IDE6MzEsICIwMiIgPSAzMjo1OSwgIjAzIiA9IDYwOjkwLCAiMDQiID0gOTE6MTIwLCANCiAgICAgICAgICAgICAgICAgICAgICAiMDUiID0gMTIxOjE1MSwgIjA2IiA9IDE1MjoxODEsICIwNyIgPSAxODI6MjEyLCANCiAgICAgICAgICAgICAgICAgICAgICAiMDgiID0gMjEzOjI0MywgIjA5IiA9IDI0NDoyNzMsICIxMCIgPSAyNzQ6MzA0LA0KICAgICAgICAgICAgICAgICAgICAgICIxMSIgPSAzMDU6MzM0LCAiMTIiID0gMzM1OjM2NSkpIHsNCiAgDQogIG1vbnRobHlfZGYgPC0gR0FNX2RhdGEgJT4lDQogICAgbXV0YXRlKG1vbnRoID0gcHVycnI6Om1hcF9jaHIoRE9ZLCBmdW5jdGlvbihkb3kpIHsNCiAgICAgIG1vbnRoX25hbWUgPC0gbmFtZXMobW9udGhseV9kb3lzKVtzYXBwbHkobW9udGhseV9kb3lzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24ocikgZG95ICVpbiUgcildDQogICAgICBpZiAobGVuZ3RoKG1vbnRoX25hbWUpID4gMCkgbW9udGhfbmFtZSBlbHNlIE5BX2NoYXJhY3Rlcl8NCiAgICB9KSkgJT4lDQogICAgZHBseXI6OmZpbHRlcighaXMubmEobW9udGgpKSAlPiUNCiAgICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCwgaW5kZXgsIG1vbnRoKSAlPiUNCiAgICBzdW1tYXJpc2UoYXZnX3ZhbHVlID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogICAgbXV0YXRlKGF2Z192YWx1ZSA9IGlmZWxzZShpcy5pbmZpbml0ZShhdmdfdmFsdWUpLCBOQSwgYXZnX3ZhbHVlKSkgJT4lDQogICAgYXJyYW5nZShQbG90T2JzZXJ2YXRpb25JRCwgbWF0Y2gobW9udGgsIG5hbWVzKG1vbnRobHlfZG95cykpKSAlPiUNCiAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbW9udGgsIHZhbHVlc19mcm9tID0gYXZnX3ZhbHVlLCANCiAgICAgICAgICAgICAgICBuYW1lc19wcmVmaXggPSAiYXZnX3ZhbHVlXyIpDQogIA0KICAjIENhbGN1bGFyIEFVQyBlbnRyZSBtYXJ6byB5IG9jdHVicmUgdXNhbmRvIHJlZ2xhIGRlbCB0cmFwZWNpbw0KICBtb250aHNfYXVjIDwtIGMoIjAzIiwgIjA0IiwgIjA1IiwgIjA2IiwgIjA3IiwgIjA4IiwgIjA5IiwgIjEwIikNCiAgIyBET1kgYXByb3hpbWFkbyBkZWwgY2VudHJvIGRlIGNhZGEgbWVzDQogIGRveV9taWRwb2ludHMgPC0gYyg3NSwgMTA1LCAxMzUsIDE2NSwgMTk1LCAyMjUsIDI1NSwgMjg1KSAgDQogIA0KICBtb250aGx5X2RmIDwtIG1vbnRobHlfZGYgJT4lDQogICAgcm93d2lzZSgpICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIGF1Y19tYXJfb2N0ID0gew0KICAgICAgICB2YWx1ZXMgPC0gY19hY3Jvc3MoYWxsX29mKHBhc3RlMCgiYXZnX3ZhbHVlXyIsIG1vbnRoc19hdWMpKSkNCiAgICAgICAgaWYgKGFueShpcy5uYSh2YWx1ZXMpKSkgTkFfcmVhbF8gZWxzZSBzdW0oZGlmZihkb3lfbWlkcG9pbnRzKSAqDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgem9vOjpyb2xsbWVhbih2YWx1ZXMsIDIpKQ0KICAgICAgfQ0KICAgICkgJT4lDQogICAgdW5ncm91cCgpDQogIA0KICByZXR1cm4obW9udGhseV9kZikNCn0NCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KbW9udGhseV9hdmdfaW5kaWNlcyA8LSBleHRyYWN0X21vbnRobHlfYXZnX2luZGljZXMoR0FNX2RhdGEpDQpgYGANCg0KU2F2ZSBhcyBhbiBvYmplY3Q6DQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpzYXZlKG1vbnRobHlfYXZnX2luZGljZXMsIGZpbGUgPSAib2JqZWN0cy9tb250aGx5X2F2Z19pbmRpY2VzX1MyLlJkYXRhIikNCmBgYA0KDQojIyBBc3Nlc3MgdGltZSBzZXJpZXMgcXVhbGl0eQ0KDQpGb3IgdGhlIHRpbWUgc2VyaWVzIHRvIGJlIGFjY2VwdGFibGUsIGl0IHNob3VsZCBoYXZlIGEgcmVhc29uYWJsZSBudW1iZXIgb2YgdGltZSBwb2ludHMsIGFuZCB0aGVzZSBwb2ludHMgc2hvdWxkIGJlIGRpc3RyaWJ1dGVkIGFsb25nIGFsbW9zdCBhbGwgbW9udGhzIChjb3VsZCBiZSBvayB0byBtaXNzIHRoZSB3aW50ZXIgbW9udGhzKS4NCg0KSW4gR0FNIGRhdGEsIGNoZWNrIGhvdyBtYW55IHRpbWUgcG9pbnRzIGFyZSB0aGVyZSBmb3IgZWFjaCBQbG90T2JzZXJ2YXRpb25JRCwgaG93IG1hbnkgbW9udGhzLCBhbmQgd2hpY2ggbW9udGhzIGFyZSBtaXNzaW5nLg0KDQpgYGB7cn0NCnRzX3F1YWxpdHkgPC0gR0FNX2RhdGEgJT4lDQogICMgRmlsdGVyIG9ubHkgTkRWSSAoYWxsIGluZGljZXMgd2lsbCBoYXZlIHRoZSBzYW1lIHRpbWUgcG9pbnRzKQ0KICBkcGx5cjo6ZmlsdGVyKGluZGV4ID09ICJORFZJIikgJT4lDQogICMgR2V0IG1vbnRoIGZyb20gRE9ZDQogIG11dGF0ZShtb250aCA9IG1vbnRoKHltZCgiMjAyMC0wMS0wMSIpICsgZGF5cyhET1kgLSAxKSkpICU+JQ0KICAjIEZvciBlYWNoIFBsb3RPYnNlcnZhdGlvbklEDQogIGdyb3VwX2J5KFBsb3RPYnNlcnZhdGlvbklEKSAlPiUNCiAgIyBHZXQgdGhlIG51bWJlciBvZiB0aW1lIHBvaW50cyAoZGF5cykgYW5kIHRoZSBudW1iZXIgb2YgbW9udGhzDQogIHN1bW1hcmlzZSgNCiAgICBuX2RheXMgPSBuX2Rpc3RpbmN0KERPWSksDQogICAgbl9tb250aHMgPSBuX2Rpc3RpbmN0KG1vbnRoKSwNCiAgICAuZ3JvdXBzID0gImRyb3AiDQogICkgJT4lDQogIGxlZnRfam9pbihHQU1fZGF0YSAlPiUNCiAgICAgICAgICAgICAgIyBGaWx0ZXIgb25seSBORFZJDQogICAgICAgICAgICAgIGRwbHlyOjpmaWx0ZXIoaW5kZXggPT0gIk5EVkkiKSAlPiUNCiAgICAgICAgICAgICAgIyBHZXQgbW9udGggZnJvbSBET1kNCiAgICAgICAgICAgICAgbXV0YXRlKG1vbnRoID0gbW9udGgoeW1kKCIyMDIwLTAxLTAxIikgKyBkYXlzKERPWSAtIDEpKSkgJT4lDQogICAgICAgICAgICAgICMgR2V0IHVuaXF1ZSB2YWx1ZXMgb2YgUGxvdE9ic2VydmF0aW9uSUQgYW5kIG1vbnRoDQogICAgICAgICAgICAgIGRpc3RpbmN0KFBsb3RPYnNlcnZhdGlvbklELCBtb250aCkgJT4lDQogICAgICAgICAgICAgICMgQWRkIDEgYXMgdmFsdWUNCiAgICAgICAgICAgICAgbXV0YXRlKHZhbHVlID0gMSkgJT4lDQogICAgICAgICAgICAgICMgUmVzaGFwZSB0byB3aWRlIGZvcm1hdCBhbmQgYWRkIHplcm9zIHdoZW4gbW9udGggaXMgbWlzc2luZw0KICAgICAgICAgICAgICBwaXZvdF93aWRlcigNCiAgICAgICAgICAgICAgICBuYW1lc19mcm9tID0gbW9udGgsDQogICAgICAgICAgICAgICAgbmFtZXNfcHJlZml4ID0gIm1vbnRoIiwNCiAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IHZhbHVlLA0KICAgICAgICAgICAgICAgIHZhbHVlc19maWxsID0gMCksDQogICAgICAgICAgICBieSA9ICJQbG90T2JzZXJ2YXRpb25JRCIpDQpgYGANCg0KSGlzdG9ncmFtcyB0aW1lIHBvaW50cyBhbmQgbiBtb250aHM6DQoNCmBgYHtyfQ0KZ2dwbG90KHRzX3F1YWxpdHksIGFlcyh4ID0gbl9kYXlzKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShjb2xvciA9ICJibGFjayIsIGZpbGwgPSAid2hpdGUiKSArDQogIHhsYWIoIk51bWJlciBvZiB0aW1lIHBvaW50cyAoZGF5cykgaW4gdGhlIFMyIHRpbWUgc2VyaWVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmdncGxvdCh0c19xdWFsaXR5LCBhZXMoeCA9IG5fbW9udGhzKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShjb2xvciA9ICJibGFjayIsIGZpbGwgPSAid2hpdGUiKSArDQogIHhsYWIoIk51bWJlciBvZiBtb250aHMgaW4gdGhlIFMyIHRpbWUgc2VyaWVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpDb3VudCBob3cgbWFueSBQbG90T2JzZXJ2YXRpb25JRHMgaGF2ZSBtaXNzaW5nIGRhdGEgKHZhbHVlIDApIGZvciBlYWNoIG1vbnRoOg0KDQpgYGB7cn0NCm9ic19taXNzaW5nX21vbnRoIDwtIHRzX3F1YWxpdHkgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3Moc3RhcnRzX3dpdGgoIm1vbnRoIiksIH4gc3VtKC54ID09IDApKSkgJT4lDQogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLCBuYW1lc190byA9ICJtb250aCIsIHZhbHVlc190byA9ICJub2JzX21pc3NpbmciKQ0KDQpnZ3Bsb3Qob2JzX21pc3NpbmdfbW9udGggJT4lDQogICAgICAgICBtdXRhdGUobW9udGggPSBmYWN0b3IobW9udGgsIGxldmVscyA9IHBhc3RlMCgibW9udGgiLCAxOjEyKSkpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbm9ic19taXNzaW5nKSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICB5bGFiKCJOdW1iZXIgb2YgUGxvdE9ic2VydmF0aW9uSUQgd2l0aCBtaXNzaW5nIGRhdGEiKSArDQogIGdndGl0bGUoIk1pc3NpbmcgZGF0YSBpbiBTMiB0aW1lIHNlcmllcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KQWRkIHF1YWxpdHkgZmxhZzoNCg0KYGBge3J9DQp0c19xdWFsaXR5X2ZsYWcgPC0gdHNfcXVhbGl0eSAlPiUNCiAgcm93d2lzZSgpICU+JQ0KICBtdXRhdGUoDQogICAgIyAgSWYgMiBjb25zZWN1dGl2ZSBtb250aHMgb2YgdGhlIHBlcmlvZCBNYXJjaC1PY3RvYmVyIGFyZSBtaXNzaW5nDQogICAgIyBxdWFsaXR5X2ZsYWcgPSAwDQogICAgcXVhbGl0eV9mbGFnID0gew0KICAgICAgbW9udGhzIDwtIGNfYWNyb3NzKG1vbnRoMzptb250aDEwKQ0KICAgICAgaWYgKGFueShtb250aHNbLWxlbmd0aChtb250aHMpXSA9PSAwICYgbW9udGhzWy0xXSA9PSAwKSkgMCBlbHNlIDENCiAgICB9DQogICkgJT4lDQogIHVuZ3JvdXAoKQ0KYGBgDQoNCmBgYHtyfQ0KdHNfcXVhbGl0eV9mbGFnICU+JSBjb3VudChxdWFsaXR5X2ZsYWcpDQpgYGANCg0KIyMgQm94cGxvdCBjb21wYXJpbmcgbW9tZW50cyBmb3IgZGlmZmVyZW50IGluZGljZXMNCg0KYGBge3J9DQpHQU1fZGF0YSAlPiUgDQogIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgaW5kZXgsIHNvc19zbG9wZSwgc29zX3RocmVzaG9sZCwgcG9zLCBlb3Nfc2xvcGUsDQogICAgICAgICBlb3NfdGhyZXNob2xkKSAlPiUgZGlzdGluY3QoKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKHNvc19zbG9wZSwgc29zX3RocmVzaG9sZCwgcG9zLCBlb3Nfc2xvcGUsIGVvc190aHJlc2hvbGQpLA0KICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibW9tZW50IiwgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lDQogIGdncGxvdChhZXMoeCA9IG1vbWVudCwgeSA9IHZhbHVlLCBmaWxsID0gaW5kZXgpKSArIGdlb21fYm94cGxvdCgpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KIyMgUGxvdCBmaXQgYW5kIG1vbWVudHMgZm9yIGVhY2ggUGxvdE9ic2VydmF0aW9uSUQNCg0KIyMjIFF1YWxpdHkgPSAxDQoNCmBgYHtyfQ0KIyBHZXQgdW5pcXVlIElEcyB3aXRoIHF1YWxpdHlfZmxhZyA9PSAxDQppZHNfcTEgPC0gdHNfcXVhbGl0eV9mbGFnICU+JQ0KICBkcGx5cjo6ZmlsdGVyKHF1YWxpdHlfZmxhZyA9PSAxKSAlPiUNCiAgbXV0YXRlKFBsb3RPYnNlcnZhdGlvbklEID0gZHJvcGxldmVscyhQbG90T2JzZXJ2YXRpb25JRCkpICU+JQ0KICBwdWxsKFBsb3RPYnNlcnZhdGlvbklEKQ0KR0FNX2RhdGFfaWRzX3ExIDwtIEdBTV9kYXRhICU+JQ0KICAjIEpvaW4gdG8gZ2V0IGJpb2dlbyBhbmQgdW5pdA0KICBsZWZ0X2pvaW4oZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIGJpb2dlbywgdW5pdCkgJT4lDQogICAgICAgICAgICAgIGRpc3RpbmN0KCkpICU+JQ0KICAjIEpvaW4gdG8gZ2V0IEVVTklTIGluZm8NCiAgIGxlZnRfam9pbihkYl9FdXJvcGFfYWxsb2JzICU+JQ0KICAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IsDQogICAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyKSkgJT4lDQogICMgSm9pbiB0byBnZXQgb3JpZ2luYWwgdmFsdWVzIG9mIGluZGljZXMNCiAgbGVmdF9qb2luKGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBET1ksIE5EVkksIEVWSSwgU0FWSSkgJT4lDQogICAgICAgICAgICAgIHBpdm90X2xvbmdlcihjb2xzID0gYyhORFZJLCBFVkksIFNBVkkpLCBuYW1lc190byA9ICJpbmRleCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlX29yaWciKSkgJT4lDQogICMgSm9pbiB0byBnZXQgdHNfcXVhbGl0eSBkYXRhDQogIGxlZnRfam9pbih0c19xdWFsaXR5X2ZsYWcgJT4lIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgcXVhbGl0eV9mbGFnKSkgJT4lDQogICMgS2VlcCBvbmx5IHRob3NlIHdpdGggcXVhbGl0eV9mbGFnID09IDENCiAgZHBseXI6OmZpbHRlcihxdWFsaXR5X2ZsYWcgPT0gMSkNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBHZXQgdW5pcXVlIFBsb3RPYnNlcnZhdGlvbklEcw0KdW5pcXVlX2lkczEgPC0gaWRzX3ExDQoNCiMgQ3JlYXRlIGFuZCBzdG9yZSBwbG90cyBpbiBhIGxpc3QNCnRzX3Bsb3RzX3ExIDwtIG1hcCh1bmlxdWVfaWRzMSwgZnVuY3Rpb24oaWQpIHsNCiAgcGxvdF9kYXRhIDwtIEdBTV9kYXRhX2lkc19xMSAlPiUNCiAgICBtdXRhdGUoUGxvdE9ic2VydmF0aW9uSUQgPSBhcy5jaGFyYWN0ZXIoUGxvdE9ic2VydmF0aW9uSUQpKSAlPiUNCiAgICBkcGx5cjo6ZmlsdGVyKFBsb3RPYnNlcnZhdGlvbklEID09IGlkKSANCiAgDQogICMgRXh0cmFjdCBtZXRhZGF0YSBmb3IgdGl0bGUNCiAgbWV0YWRhdGEgPC0gcGxvdF9kYXRhICU+JQ0KICAgIHNlbGVjdChiaW9nZW8sIHVuaXQsIEVVTklTYV8xLCBFVU5JU2FfMiwgcXVhbGl0eV9mbGFnKSAlPiUNCiAgICBkaXN0aW5jdCgpDQogIA0KICBnZ3Bsb3QoKSArDQogICAgIyBSYXcgZGF0YSBwb2ludHMNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBwbG90X2RhdGEsYWVzKHggPSBET1ksIHkgPSB2YWx1ZV9vcmlnKSwgYWxwaGEgPSAwLjUpICsNCiAgICBnZW9tX2xpbmUoZGF0YSA9IHBsb3RfZGF0YSwgYWVzKHggPSBET1ksIHkgPSB2YWx1ZSksIA0KICAgICAgICAgICAgICBzaXplID0gMC41LCBjb2xvciA9ICJibHVlIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIHNvc19zbG9wZSksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvc19zbG9wZSwgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgc29zX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvc190aHJlc2hvbGQsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZGFya2dyZWVuIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIHBvcyksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHBvcywgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJibHVlIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIGVvc19zbG9wZSksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IGVvc19zbG9wZSwgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgZW9zX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IGVvc190aHJlc2hvbGQsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZGFya2dyZWVuIikgKw0KICAgIGZhY2V0X2dyaWQoY29scyA9IHZhcnMoaW5kZXgpKSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gZ2x1ZTo6Z2x1ZSgie2lkfSB8IHttZXRhZGF0YSRiaW9nZW99e21ldGFkYXRhJHVuaXR9IHwge21ldGFkYXRhJEVVTklTYV8xfSB8IHttZXRhZGF0YSRFVU5JU2FfMn0gfCBRdWFsaXR5OiB7bWV0YWRhdGEkcXVhbGl0eV9mbGFnfSIpLA0KICAgICAgeCA9ICJEYXkgb2YgWWVhciIsDQogICAgICB5ID0gIkluZGV4IFZhbHVlIg0KICAgICkgKw0KICAgIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQ0KfSkNCg0KIyBOYW1lIHRoZSBsaXN0IGJ5IFBsb3RPYnNlcnZhdGlvbklEDQpuYW1lcyh0c19wbG90c19xMSkgPC0gdW5pcXVlX2lkczENCg0KIyBEaXNwbGF5IHRoZSBmaXJzdCBwbG90DQp0c19wbG90c19xMVsxXQ0KYGBgDQoNClNhdmUgZWFjaCBwbG90IHRvIGEgZmlsZToNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCndhbGsyKHRzX3Bsb3RzX3ExLCBzZXFfYWxvbmcodHNfcGxvdHNfcTEpLCB+IGdnc2F2ZSgNCiAgZmlsZW5hbWUgPSBwYXN0ZTAoIkM6L0FuYWx5c2VzL01PVElWQVRFX3ZhbGlkYXRpb24vb3V0cHV0L2ZpZ3VyZXMvcGhlbm9sb2d5L3RzX3ExX1MyL3RzX3Bsb3RzX3ExIiwgLnksICIuanBlZyIpLA0KICBwbG90ID0gLngsDQogIHdpZHRoID0gOCwNCiAgaGVpZ2h0ID0gNQ0KKSkNCmBgYA0KDQojIyMgUXVhbGl0eSA9IDANCg0KYGBge3J9DQojIEdldCB1bmlxdWUgSURzIHdpdGggcXVhbGl0eV9mbGFnID09IDANCmlkc19xMCA8LSB0c19xdWFsaXR5X2ZsYWcgJT4lDQogIGRwbHlyOjpmaWx0ZXIocXVhbGl0eV9mbGFnID09IDApICU+JQ0KICBtdXRhdGUoUGxvdE9ic2VydmF0aW9uSUQgPSBkcm9wbGV2ZWxzKFBsb3RPYnNlcnZhdGlvbklEKSkgJT4lDQogIHB1bGwoUGxvdE9ic2VydmF0aW9uSUQpDQpHQU1fZGF0YV9pZHNfcTAgPC0gR0FNX2RhdGEgJT4lDQogICMgSm9pbiB0byBnZXQgYmlvZ2VvIGFuZCB1bml0DQogIGxlZnRfam9pbihkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMgJT4lDQogICAgICAgICAgICAgIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgYmlvZ2VvLCB1bml0KSAlPiUNCiAgICAgICAgICAgICAgZGlzdGluY3QoKSkgJT4lDQogICMgSm9pbiB0byBnZXQgRVVOSVMgaW5mbw0KICAgbGVmdF9qb2luKGRiX0V1cm9wYV9hbGxvYnMgJT4lDQogICAgICAgICAgICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIEVVTklTYV8xLCBFVU5JU2FfMV9kZXNjciwNCiAgICAgICAgICAgICAgICAgICAgICBFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IpKSAlPiUNCiAgIyBKb2luIHRvIGdldCBvcmlnaW5hbCB2YWx1ZXMgb2YgaW5kaWNlcw0KICBsZWZ0X2pvaW4oZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIERPWSwgTkRWSSwgRVZJLCBTQVZJKSAlPiUNCiAgICAgICAgICAgICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKE5EVkksIEVWSSwgU0FWSSksIG5hbWVzX3RvID0gImluZGV4IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWVfb3JpZyIpKSAlPiUNCiAgIyBKb2luIHRvIGdldCB0c19xdWFsaXR5IGRhdGENCiAgbGVmdF9qb2luKHRzX3F1YWxpdHlfZmxhZyAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBuX21vbnRocywgcXVhbGl0eV9mbGFnKSkgJT4lDQogICMgS2VlcCBvbmx5IHRob3NlIHdpdGggcXVhbGl0eV9mbGFnID09IDANCiAgZHBseXI6OmZpbHRlcihxdWFsaXR5X2ZsYWcgPT0gMCkNCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KIyBHZXQgdW5pcXVlIFBsb3RPYnNlcnZhdGlvbklEcw0KdW5pcXVlX2lkczAgPC0gaWRzX3EwDQoNCiMgQ3JlYXRlIGFuZCBzdG9yZSBwbG90cyBpbiBhIGxpc3QNCnRzX3Bsb3RzX3EwIDwtIG1hcCh1bmlxdWVfaWRzMCwgZnVuY3Rpb24oaWQpIHsNCiAgcGxvdF9kYXRhIDwtIEdBTV9kYXRhX2lkc19xMCAlPiUNCiAgICBtdXRhdGUoUGxvdE9ic2VydmF0aW9uSUQgPSBhcy5jaGFyYWN0ZXIoUGxvdE9ic2VydmF0aW9uSUQpKSAlPiUNCiAgICBkcGx5cjo6ZmlsdGVyKFBsb3RPYnNlcnZhdGlvbklEID09IGlkKSANCiAgDQogICMgRXh0cmFjdCBtZXRhZGF0YSBmb3IgdGl0bGUNCiAgbWV0YWRhdGEgPC0gcGxvdF9kYXRhICU+JQ0KICAgIHNlbGVjdChiaW9nZW8sIHVuaXQsIEVVTklTYV8xLCBFVU5JU2FfMiwgcXVhbGl0eV9mbGFnKSAlPiUNCiAgICBkaXN0aW5jdCgpDQogIA0KICBnZ3Bsb3QoKSArDQogICAgIyBSYXcgZGF0YSBwb2ludHMNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBwbG90X2RhdGEsYWVzKHggPSBET1ksIHkgPSB2YWx1ZV9vcmlnKSwgYWxwaGEgPSAwLjUpICsNCiAgICBnZW9tX2xpbmUoZGF0YSA9IHBsb3RfZGF0YSwgYWVzKHggPSBET1ksIHkgPSB2YWx1ZSksIA0KICAgICAgICAgICAgICBzaXplID0gMC41LCBjb2xvciA9ICJibHVlIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIHNvc19zbG9wZSksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvc19zbG9wZSwgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgc29zX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvc190aHJlc2hvbGQsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZGFya2dyZWVuIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIHBvcyksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHBvcywgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJibHVlIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IHBsb3RfZGF0YSAlPiUgZGlzdGluY3QoaW5kZXgsIGVvc19zbG9wZSksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IGVvc19zbG9wZSwgZ3JvdXAgPSBpbmRleCksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gcGxvdF9kYXRhICU+JSBkaXN0aW5jdChpbmRleCwgZW9zX3RocmVzaG9sZCksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IGVvc190aHJlc2hvbGQsIGdyb3VwID0gaW5kZXgpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZGFya2dyZWVuIikgKw0KICAgIGZhY2V0X2dyaWQoY29scyA9IHZhcnMoaW5kZXgpKSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gZ2x1ZTo6Z2x1ZSgie2lkfSB8IHttZXRhZGF0YSRiaW9nZW99e21ldGFkYXRhJHVuaXR9IHwge21ldGFkYXRhJEVVTklTYV8xfSB8IHttZXRhZGF0YSRFVU5JU2FfMn0gfCBRdWFsaXR5OiB7bWV0YWRhdGEkcXVhbGl0eV9mbGFnfSIpLA0KICAgICAgeCA9ICJEYXkgb2YgWWVhciIsDQogICAgICB5ID0gIkluZGV4IFZhbHVlIg0KICAgICkgKw0KICAgIHRoZW1lX21pbmltYWwoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQ0KfSkNCg0KIyBOYW1lIHRoZSBsaXN0IGJ5IFBsb3RPYnNlcnZhdGlvbklEDQpuYW1lcyh0c19wbG90c19xMCkgPC0gdW5pcXVlX2lkczANCg0KIyBEaXNwbGF5IHRoZSBmaXJzdCBwbG90DQp0c19wbG90c19xMFsxXQ0KYGBgDQoNClNhdmUgZWFjaCBwbG90IHRvIGEgZmlsZToNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCndhbGsyKHRzX3Bsb3RzX3EwLCBzZXFfYWxvbmcodHNfcGxvdHNfcTApLCB+IGdnc2F2ZSgNCiAgZmlsZW5hbWUgPSBwYXN0ZTAoIkM6L0FuYWx5c2VzL01PVElWQVRFX3ZhbGlkYXRpb24vb3V0cHV0L2ZpZ3VyZXMvcGhlbm9sb2d5L3RzX3EwX1MyL3RzX3Bsb3RzX3EwIiwgLnksICIuanBlZyIpLA0KICBwbG90ID0gLngsDQogIHdpZHRoID0gOCwNCiAgaGVpZ2h0ID0gNQ0KKSkNCmBgYA0KDQojIFNtb290aCB0aGUgdGltZSBzZXJpZXMgb2YgTkRNSSBhbmQgTkRXSQ0KDQpVc2luZyBHQU0sIHdpdGhvdXQgcmVwbGFjaW5nIHZhbHVlcyBpbiBET1kgMeKAkzUwIGFuZCBET1kgMzE14oCTZW5kIHdpdGggc2VwYXJhdGUgYmFzZSB2YWx1ZXMsIGxhdGVyIHVzZSBvbmx5IHVud2VpZ2h0ZWQgR0FNLg0KDQpgYGB7cn0NCmNvbXB1dGVfdW53ZWlnaHRlZF9maXQgPC0gZnVuY3Rpb24oDQogICAgIyBEYXRhIGZyYW1lIGRmIHdpdGggaW5kZXggdmFsdWVzIG92ZXIgdGltZSAoRE9ZKQ0KICAgIGRmLCANCiAgICAjIE5hbWUgb2YgdGhlIHZlZ2V0YXRpb24gaW5kaWNlcyBjb2x1bW5zIChlLmcuLCAiTkRWSSIsICJFVkkiLCAiU0FWSSkNCiAgICBpbmRleF9jb2xzID0gYygiTkRNSSIsICJORFdJIikNCikgew0KICAjIEluaXRpYWxpemUgbGlzdCB0byBzdG9yZSByZXN1bHRzDQogIGZpdHNfbGlzdCA8LSBsaXN0KCkNCiAgDQogICMgTG9vcCBvdmVyIGVhY2ggaW5kZXggY29sdW1uDQogIGZvciAoaW5kZXhfY29sIGluIGluZGV4X2NvbHMpIHsNCiAgICBkZl9pbmRleCA8LSBkZiAlPiUNCiAgICAgICMgUmVtb3ZlIHJvd3Mgd2l0aCBtaXNzaW5nIGluZGV4IHZhbHVlcyBhbmQgc29ydCBkYXRhIGJ5IERPWQ0KICAgICAgZmlsdGVyKCFpcy5uYSguZGF0YVtbaW5kZXhfY29sXV0pKSAlPiUgYXJyYW5nZShET1kpDQogICAgDQogICAgIyBFeHRyYWN0IHggKERPWSkgYW5kIHkgKGluZGV4KSB2ZWN0b3JzIGZvciBtb2RlbGxpbmcNCiAgICB4IDwtIGRmX2luZGV4JERPWQ0KICAgIHkgPC0gZGZfaW5kZXhbW2luZGV4X2NvbF1dDQogICAgDQogICAgIyBJZiB0aGVyZSBhcmUgZmV3ZXIgdGhhbiAxMSBvYnNlcnZhdGlvbnMgb3IgYWxsIHZhbHVlcyBhcmUgTkEsIHNraXANCiAgICBpZiAobGVuZ3RoKHgpIDwgMTEgfHwgYWxsKGlzLm5hKHkpKSkgew0KICAgICAgbmV4dA0KICAgIH0NCiAgICANCiAgICAjIEZpdCBHQU0gKHVud2VpZ2h0ZWQpIHdpdGggYSB0aGluIHBsYXRlIHNwbGluZSAoYnMgPSAidHAiKQ0KICAgICMgdG8gc21vb3RoIHRoZSBpbmRleCBjdXJ2ZQ0KICAgIGdhbV91bndlaWdodGVkIDwtIG1nY3Y6OmJhbSh5IH4gcyh4LCBicyA9ICJ0cCIpKQ0KICAgIHByZWQgPC0gcHJlZGljdChnYW1fdW53ZWlnaHRlZCwgbmV3ZGF0YSA9IHRpYmJsZSh4ID0geCkpDQogICAgDQogICAgIyBDcmVhdGUgdGliYmxlIHRvIHN0b3JlIG9yaWdpbmFsIGFuZCBwcmVkaWN0ZWQgaW5kZXggdmFsdWVzDQogICAgZml0c19kZiA8LSB0aWJibGUoDQogICAgICBQbG90T2JzZXJ2YXRpb25JRCA9IHVuaXF1ZShkZiRQbG90T2JzZXJ2YXRpb25JRCksDQogICAgICBET1kgPSB4LA0KICAgICAgaW5kZXggPSBpbmRleF9jb2wsDQogICAgICB2YWx1ZSA9IHByZWQNCiAgICApDQogICAgDQogICAgZml0c19saXN0W1tpbmRleF9jb2xdXSA8LSBmaXRzX2RmDQogIH0NCiAgDQogIGlmIChsZW5ndGgoZml0c19saXN0KSA9PSAwKSB7DQogICAgcmV0dXJuKHRpYmJsZSgpKQ0KICB9DQogIA0KICBiaW5kX3Jvd3MoZml0c19saXN0KQ0KfQ0KYGBgDQoNCkFwcGx5IHRoZSBmdW5jdGlvbjoNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnBsYW4obXVsdGlzZXNzaW9uLCB3b3JrZXJzID0gcGFyYWxsZWw6OmRldGVjdENvcmVzKCkgLSAxKQ0KDQojIEFwcGx5IHRoZSBmdW5jdGlvbiB0byBlYWNoIFBsb3RPYnNlcnZhdGlvbklEDQpleGVjdXRpb25fdGltZSA8LSBzeXN0ZW0udGltZSh7DQogIHdpdGhfcHJvZ3Jlc3Moew0KICAgIHNtb290aGVkX2RhdGEgPC0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgZ3JvdXBfc3BsaXQoUGxvdE9ic2VydmF0aW9uSUQpICU+JQ0KICAgICAgc2V0X25hbWVzKG1hcF9jaHIoLiwgfiBhcy5jaGFyYWN0ZXIodW5pcXVlKC54JFBsb3RPYnNlcnZhdGlvbklEKSkpKSAlPiUNCiAgICAgIGZ1dHVyZV9tYXBfZGZyKH4gY29tcHV0ZV91bndlaWdodGVkX2ZpdChkZiA9IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbmRleF9jb2xzID0gYygiTkRNSSIsICJORFdJIikpLA0KICAgICAgICAgICAgICAgICAgICAgLnByb2dyZXNzID0gVFJVRSkNCiAgfSkNCn0pDQoNCnByaW50KGV4ZWN1dGlvbl90aW1lKQ0KYGBgDQoNCkxvb2s6DQoNCmBgYHtyfQ0Kc21vb3RoZWRfZGF0YQ0KYGBgDQoNClNhdmUgYXMgYW4gb2JqZWN0Og0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0Kc2F2ZShzbW9vdGhlZF9kYXRhLCBmaWxlID0gIm9iamVjdHMvc21vb3RoZWRfZGF0YV9TMi5SZGF0YSIpDQpgYGANCg0KIyMgUGxvdCBmaXQgYW5kIG1vbWVudHMgZm9yIGVhY2ggUGxvdE9ic2VydmF0aW9uSUQNCg0KYGBge3J9DQpzbW9vdGhlZF9kYXRhX2lkcyA8LSBzbW9vdGhlZF9kYXRhICU+JQ0KICAjIEpvaW4gdG8gZ2V0IGJpb2dlbyBhbmQgdW5pdA0KICBsZWZ0X2pvaW4oZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIGJpb2dlbywgdW5pdCkgJT4lDQogICAgICAgICAgICAgIGRpc3RpbmN0KCkpICU+JQ0KICAjIEpvaW4gdG8gZ2V0IEVVTklTIGluZm8NCiAgIGxlZnRfam9pbihkYl9FdXJvcGFfYWxsb2JzICU+JQ0KICAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IsDQogICAgICAgICAgICAgICAgICAgICAgRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyKSkgJT4lDQogIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGFzLmNoYXJhY3RlcihQbG90T2JzZXJ2YXRpb25JRCkpDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCiMgR2V0IHVuaXF1ZSBQbG90T2JzZXJ2YXRpb25JRHMNCnVuaXF1ZV9pZHMgPC0gdW5pcXVlKHNtb290aGVkX2RhdGFfaWRzJFBsb3RPYnNlcnZhdGlvbklEKQ0KDQojIENyZWF0ZSBhbmQgc3RvcmUgcGxvdHMgaW4gYSBsaXN0DQp0c19wbG90c19ORE1JX05EV0k8LSBtYXAodW5pcXVlX2lkcywgZnVuY3Rpb24oaWQpIHsNCiAgcGxvdF9kYXRhIDwtIHNtb290aGVkX2RhdGFfaWRzICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKFBsb3RPYnNlcnZhdGlvbklEID09IGlkKQ0KICANCiAgIyBFeHRyYWN0IG1ldGFkYXRhIGZvciB0aXRsZQ0KICBtZXRhZGF0YSA8LSBwbG90X2RhdGEgJT4lDQogICAgc2VsZWN0KGJpb2dlbywgdW5pdCwgRVVOSVNhXzEsIEVVTklTYV8yKSAlPiUNCiAgICBkaXN0aW5jdCgpDQogIA0KICBnZ3Bsb3QoKSArDQogICAgIyBSYXcgZGF0YSBwb2ludHMNCiAgICAgZ2VvbV9wb2ludChkYXRhID0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICAgICAgICAgICAgICAgICAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBET1ksIE5ETUksIE5EV0kpICU+JQ0KICAgICAgICAgICAgICAgICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKE5ETUksIE5EV0kpLCBuYW1lc190byA9ICJpbmRleCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lDQogICAgICAgICAgICAgICAgICBmaWx0ZXIoUGxvdE9ic2VydmF0aW9uSUQgPT0gaWQpLA0KICAgICAgICAgICAgICAgIGFlcyh4ID0gRE9ZLCB5ID0gdmFsdWUpLCBhbHBoYSA9IDAuNikgKw0KICAgIGdlb21fbGluZShkYXRhID0gcGxvdF9kYXRhLCBhZXMoeCA9IERPWSwgeSA9IHZhbHVlKSwNCiAgICAgICAgICAgICAgc2l6ZSA9IDAuNSwgY29sb3IgPSAiYmx1ZSIpICsNCiAgICBmYWNldF9ncmlkKGNvbHMgPSB2YXJzKGluZGV4KSkgKw0KICAgIGxhYnMoDQogICAgICB0aXRsZSA9IGdsdWU6OmdsdWUoIntpZH0gfCB7bWV0YWRhdGEkYmlvZ2VvfXttZXRhZGF0YSR1bml0fSB8IHttZXRhZGF0YSRFVU5JU2FfMX0gfCB7bWV0YWRhdGEkRVVOSVNhXzJ9IiksDQogICAgICB4ID0gIkRheSBvZiBZZWFyIiwNCiAgICAgIHkgPSAiSW5kZXggVmFsdWUiDQogICAgKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIpDQp9KQ0KDQojIE5hbWUgdGhlIGxpc3QgYnkgUGxvdE9ic2VydmF0aW9uSUQNCm5hbWVzKHRzX3Bsb3RzX05ETUlfTkRXSSkgPC0gdW5pcXVlX2lkcw0KDQojIERpc3BsYXkgdGhlIGZpcnN0IHBsb3QNCnByaW50KHRzX3Bsb3RzX05ETUlfTkRXSVtbMV1dKQ0KYGBgDQoNClNhdmUgZWFjaCBwbG90IHRvIGEgZmlsZToNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCndhbGsyKHRzX3Bsb3RzX05ETUlfTkRXSSwgc2VxX2Fsb25nKHRzX3Bsb3RzX05ETUlfTkRXSSksIH4gZ2dzYXZlKA0KICBmaWxlbmFtZSA9IHBhc3RlMCgiQzovQW5hbHlzZXMvTU9USVZBVEVfdmFsaWRhdGlvbi9vdXRwdXQvZmlndXJlcy9waGVub2xvZ3kvdHNfTkRNSV9ORFdJX1MyL3RzX3Bsb3RzX05EVklfTkRNSSIsDQogICAgICAgICAgICAgICAgICAgIC55LCAiLmpwZWciKSwNCiAgcGxvdCA9IC54LA0KICB3aWR0aCA9IDgsDQogIGhlaWdodCA9IDUNCikpDQpgYGANCg0KIyBHZXQgaW5kaWNlcyBkYXRhIChtYXguIGFuZCBtaW4uKQ0KDQpDYXJlZnVsISBUaGVzZSBtYXhpbXVtIGFuZCBtaW5pbXVtIHZhbHVlcyBhcmUgZnJvbSB0aGUgc21vb3RoZWQgdGltZSBzZXJpZXMuIEZvciBORFZJIC8gRVZJIC8gU0FWSSB2YWx1ZXMgaW4gRE9ZIDHigJM1MCBhbmQgRE9ZIDMxNeKAk2VuZCwgcmVtZW1iZXIgdGhhdCB0aGUgR0FNIHNtb290aGluZyBmdW5jdGlvbiByZXBsYWNlZCB0aGUgb3JpZ2luYWwgdmFsdWVzIHdpdGggdGhlIG1lYW4gYmFzZSB2YWx1ZSBvZiBvYnNlcnZhdGlvbnMgZHVyaW5nIGVhY2ggb2YgdGhlc2UgcmVzcGVjdGl2ZSBwZXJpb2RzLiBUaGlzIHdhcyBzbyBmYXIgbm90IGRvbmUgZm9yIE5ETUkgYW5kIE5EV0kuIA0KDQpgYGB7cn0NCmZpbmFsX2luZGljZXNfZGF0YSA8LSBHQU1fZGF0YSAlPiUNCiAgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4KSAlPiUNCiAgc3VtbWFyaXNlKG1heCA9IG1heCh2YWx1ZSksIG1pbiA9IG1pbih2YWx1ZSkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBpbmRleCwgdmFsdWVzX2Zyb20gPSBjKG1heCwgbWluKSwNCiAgICAgICAgICAgICAgbmFtZXNfZ2x1ZSA9ICJ7aW5kZXh9X3sudmFsdWV9IikgJT4lDQogIGZ1bGxfam9pbigNCiAgICBzbW9vdGhlZF9kYXRhICU+JQ0KICAgICAgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4KSAlPiUNCiAgICAgIHN1bW1hcmlzZShtYXggPSBtYXgodmFsdWUpLCBtaW4gPSBtaW4odmFsdWUpKSAlPiUNCiAgICAgIHVuZ3JvdXAoKSAlPiUNCiAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBpbmRleCwgdmFsdWVzX2Zyb20gPSBjKG1heCwgbWluKSwNCiAgICAgICAgICAgICAgICAgIG5hbWVzX2dsdWUgPSAie2luZGV4fV97LnZhbHVlfSIpDQogICAgKQ0KYGBgDQoNCiMgR2V0IHBoZW5vbG9neSBkYXRhDQoNClVzZSBHQU0gaXRlcl8zIHRvIGdldCBkYXRlcyBvZiB0aGUgbW9tZW50cywgdmFsdWVzIGF0IHRob3NlIG1vbWVudHMgYW5kIEFVQyAodGltZS1pbnRlZ3JhdGVkIGluZGljZXMpIGJldHdlZW4gU09TIGFuZCBFT1M6DQoNCmBgYHtyfQ0KIyBKb2luIHRvIGdldCB2YWx1ZXMgYXQgU09TLCBQT1MsIEVPUyBhbmQgYXVjDQpmaW5hbF9waGVub2xvZ3lfZGF0YSA8LSBHQU1fZGF0YSAlPiUNCiAgbXV0YXRlKA0KICAgIHN0YWdlID0gY2FzZV93aGVuKA0KICAgICAgRE9ZID09IHNvc19zbG9wZSB+ICJzb3Nfc2xvcGUiLA0KICAgICAgRE9ZID09IHNvc190aHJlc2hvbGQgfiAic29zX3RocmVzaG9sZCIsDQogICAgICBET1kgPT0gcG9zIH4gInBvcyIsDQogICAgICBET1kgPT0gZW9zX3Nsb3BlIH4gImVvc19zbG9wZSIsDQogICAgICBET1kgPT0gZW9zX3RocmVzaG9sZCB+ICJlb3NfdGhyZXNob2xkIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfDQogICAgKQ0KICApICU+JQ0KICBkcGx5cjo6ZmlsdGVyKCFpcy5uYShzdGFnZSkpICU+JQ0KICBzZWxlY3QoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4LCBzdGFnZSwgZG95ID0gRE9ZLCB2YWx1ZSkgJT4lDQogIHBpdm90X3dpZGVyKA0KICAgIG5hbWVzX2Zyb20gPSBjKGluZGV4LCBzdGFnZSksDQogICAgdmFsdWVzX2Zyb20gPSBjKGRveSwgdmFsdWUpLA0KICAgIG5hbWVzX2dsdWUgPSAie2luZGV4fV97c3RhZ2V9X3sudmFsdWV9Ig0KICApICU+JQ0KICAjIENvbnZlcnQgbGlzdCBjb2xzIHRvIHJlZ3VsYXIgbnVtZXJpYyBjb2xzDQogIG11dGF0ZSgNCiAgICBORFZJX3Nvc19zbG9wZV92YWx1ZSA9IG1hcF9kYmwoTkRWSV9zb3Nfc2xvcGVfdmFsdWUsIDEpLA0KICAgIE5EVklfc29zX3RocmVzaG9sZF92YWx1ZSA9IG1hcF9kYmwoTkRWSV9zb3NfdGhyZXNob2xkX3ZhbHVlLCAxKSwNCiAgICBORFZJX3Bvc192YWx1ZSA9IG1hcF9kYmwoTkRWSV9wb3NfdmFsdWUsIDEpLA0KICAgIE5EVklfZW9zX3Nsb3BlX3ZhbHVlID0gbWFwX2RibChORFZJX2Vvc19zbG9wZV92YWx1ZSwgMSksDQogICAgTkRWSV9lb3NfdGhyZXNob2xkX3ZhbHVlID0gbWFwX2RibChORFZJX2Vvc190aHJlc2hvbGRfdmFsdWUsIDEpLA0KICAgIEVWSV9zb3Nfc2xvcGVfdmFsdWUgPSBtYXBfZGJsKEVWSV9zb3Nfc2xvcGVfdmFsdWUsIDEpLA0KICAgIEVWSV9zb3NfdGhyZXNob2xkX3ZhbHVlID0gbWFwX2RibChFVklfc29zX3RocmVzaG9sZF92YWx1ZSwgMSksDQogICAgRVZJX3Bvc192YWx1ZSA9IG1hcF9kYmwoRVZJX3Bvc192YWx1ZSwgMSksDQogICAgRVZJX2Vvc19zbG9wZV92YWx1ZSA9IG1hcF9kYmwoRVZJX2Vvc19zbG9wZV92YWx1ZSwgMSksDQogICAgRVZJX2Vvc190aHJlc2hvbGRfdmFsdWUgPSBtYXBfZGJsKEVWSV9lb3NfdGhyZXNob2xkX3ZhbHVlLCAxKSwNCiAgICBTQVZJX3Nvc19zbG9wZV92YWx1ZSA9IG1hcF9kYmwoU0FWSV9zb3Nfc2xvcGVfdmFsdWUsIDEpLA0KICAgIFNBVklfc29zX3RocmVzaG9sZF92YWx1ZSA9IG1hcF9kYmwoU0FWSV9zb3NfdGhyZXNob2xkX3ZhbHVlLCAxKSwNCiAgICBTQVZJX3Bvc192YWx1ZSA9IG1hcF9kYmwoU0FWSV9wb3NfdmFsdWUsIDEpLA0KICAgIFNBVklfZW9zX3Nsb3BlX3ZhbHVlID0gbWFwX2RibChTQVZJX2Vvc19zbG9wZV92YWx1ZSwgMSksDQogICAgU0FWSV9lb3NfdGhyZXNob2xkX3ZhbHVlID0gbWFwX2RibChTQVZJX2Vvc190aHJlc2hvbGRfdmFsdWUsIDEpDQogICkgJT4lDQogIGZ1bGxfam9pbihHQU1fZGF0YSAlPiUNCiAgICAgICAgICAgICAgZGlzdGluY3QoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4LCBhdWNfc2xvcGUsIGF1Y190aHJlc2hvbGQpICU+JQ0KICAgICAgICAgICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gaW5kZXgsIHZhbHVlc19mcm9tID0gYyhhdWNfc2xvcGUsIGF1Y190aHJlc2hvbGQpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lc19nbHVlID0gIntpbmRleH1fey52YWx1ZX0iKSkNCmBgYA0KDQojIEpvaW4gaW5kaWNlcyBhbmQgcGhlbm9sb2d5IGRhdGENCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZ1bGxfam9pbigNCiAgIyBJbmRpY2VzIGRhdGEgKG1heCBhbmQgbWluKQ0KICBmaW5hbF9pbmRpY2VzX2RhdGEsDQogICMgQXZlcmFnZSB2YWx1ZXMgb2YgaW5kaWNlcyBwZXIgbW9udGgNCiAgbW9udGhseV9hdmdfaW5kaWNlcyAlPiUNCiAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gaW5kZXgsIHZhbHVlc19mcm9tID0gYyhhdmdfdmFsdWVfMDE6YXVjX21hcl9vY3QpLA0KICAgICAgICAgICAgICAgIG5hbWVzX2dsdWUgPSAie2luZGV4fV97LnZhbHVlfSIpDQogICkgJT4lDQogIGZ1bGxfam9pbigNCiAgICAjIFBoZW5vbG9neSBkYXRhDQogICAgZmluYWxfcGhlbm9sb2d5X2RhdGEgDQogICAgKSAlPiUNCiAgIyBTb3J0IGNvbHMgaW4gYWxwaGFiZXRpY2FsIG9yZGVyDQogIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgc29ydChuYW1lcyguKVtuYW1lcyguKSAhPSAiUGxvdE9ic2VydmF0aW9uSUQiXSkpDQpgYGANCg0KIyBBZGQgRVVOSVMgY29kZXMNCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZpbmFsX1JTX2RhdGEgJT4lIGxlZnRfam9pbihkYl9FdXJvcGFfYWxsb2JzKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzIDwtIGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyAlPiUNCiAgbGVmdF9qb2luKGRiX0V1cm9wYV9hbGxvYnMpDQpgYGANCg0KIyBNb250aGx5IHNwZWN0cm9waGVub2xvZ3kgcGVyIGhhYml0YXQgdHlwZQ0KDQpgYGB7cn0NCiMgUHJlcGFyZSB0aGUgZGF0YQ0KZGF0YV9tb250aGx5X0VVTklTYV8xIDwtIGRhdGFfUlNfUzJfYmFuZHNfaW5kaWNlcyAlPiUNCiAgbXV0YXRlKG1vbnRoID0gbW9udGgoZGF0ZSwgbGFiZWwgPSBUUlVFLCBhYmJyID0gVFJVRSwgbG9jYWxlPSJFTi11cyIpKSAlPiUNCiAgZ3JvdXBfYnkobW9udGgsIEVVTklTYV8xLCBFVU5JU2FfMV9kZXNjcikgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBtZWFuX05EVkkgPSBtZWFuKE5EVkksIG5hLnJtID0gVFJVRSksDQogICAgc2RfTkRWSSA9IHNkKE5EVkksIG5hLnJtID0gVFJVRSksDQogICAgbl9ORFZJID0gc3VtKCFpcy5uYShORFZJKSksDQogICAgbWVhbl9FVkkgPSBtZWFuKEVWSSwgbmEucm0gPSBUUlVFKSwNCiAgICBzZF9FVkkgPSBzZChFVkksIG5hLnJtID0gVFJVRSksDQogICAgbl9FVkkgPSBzdW0oIWlzLm5hKEVWSSkpLA0KICAgIG1lYW5fU0FWSSA9IG1lYW4oU0FWSSwgbmEucm0gPSBUUlVFKSwNCiAgICBzZF9TQVZJID0gc2QoU0FWSSwgbmEucm0gPSBUUlVFKSwNCiAgICBuX1NBVkkgPSBzdW0oIWlzLm5hKFNBVkkpKSwNCiAgICAuZ3JvdXBzID0gImRyb3AiDQogICkNCg0KZGF0YV9tb250aGx5X0VVTklTYV8xIDwtIGRhdGFfbW9udGhseV9FVU5JU2FfMSAlPiUNCiAgIyBBZGQgbGFiZWwgd2l0aCBuDQogIGxlZnRfam9pbihkYXRhX21vbnRobHlfRVVOSVNhXzEgJT4lDQogICAgICAgICAgICAgIGdyb3VwX2J5KEVVTklTYV8xKSAlPiUNCiAgICAgICAgICAgICAgc3VtbWFyaXNlKG5fdG90YWwgPSBzdW0obl9ORFZJLCBuYS5ybSA9IFRSVUUpKSwgDQogICAgICAgICAgICBieSA9ICJFVU5JU2FfMSIpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzFfbGFiZWwgPSBwYXN0ZTAoRVVOSVNhXzEsICIgKG4gPSAiLCBuX3RvdGFsLCAiKSIpKQ0KDQpkYXRhX21vbnRobHlfRVVOSVNhXzIgPC0gZGF0YV9SU19TMl9iYW5kc19pbmRpY2VzICU+JQ0KICBtdXRhdGUobW9udGggPSBtb250aChkYXRlLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBUUlVFLCBsb2NhbGU9IkVOLXVzIikpICU+JQ0KICBncm91cF9ieShtb250aCwgRVVOSVNhXzEsIEVVTklTYV8xX2Rlc2NyLCBFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgbWVhbl9ORFZJID0gbWVhbihORFZJLCBuYS5ybSA9IFRSVUUpLA0KICAgIHNkX05EVkkgPSBzZChORFZJLCBuYS5ybSA9IFRSVUUpLA0KICAgIG5fTkRWSSA9IHN1bSghaXMubmEoTkRWSSkpLA0KICAgIG1lYW5fRVZJID0gbWVhbihFVkksIG5hLnJtID0gVFJVRSksDQogICAgc2RfRVZJID0gc2QoRVZJLCBuYS5ybSA9IFRSVUUpLA0KICAgIG5fRVZJID0gc3VtKCFpcy5uYShFVkkpKSwNCiAgICBtZWFuX1NBVkkgPSBtZWFuKFNBVkksIG5hLnJtID0gVFJVRSksDQogICAgc2RfU0FWSSA9IHNkKFNBVkksIG5hLnJtID0gVFJVRSksDQogICAgbl9TQVZJID0gc3VtKCFpcy5uYShTQVZJKSksDQogICAgLmdyb3VwcyA9ICJkcm9wIg0KICApDQoNCmRhdGFfbW9udGhseV9FVU5JU2FfMiA8LSBkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lDQogICMgQWRkIGxhYmVsIHdpdGggbg0KICBsZWZ0X2pvaW4oZGF0YV9tb250aGx5X0VVTklTYV8yICU+JQ0KICAgICAgICAgICAgICBncm91cF9ieShFVU5JU2FfMikgJT4lDQogICAgICAgICAgICAgIHN1bW1hcmlzZShuX3RvdGFsID0gc3VtKG5fTkRWSSwgbmEucm0gPSBUUlVFKSksIA0KICAgICAgICAgICAgYnkgPSAiRVVOSVNhXzIiKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8yX2xhYmVsID0gcGFzdGUwKEVVTklTYV8yLCAiIChuID0gIiwgbl90b3RhbCwgIikiKSkNCmBgYA0KDQojIyBFVU5JUyBsZXZlbCAxDQoNCmBgYHtyfQ0KIyBQbG90cw0KDQojIEVVTklTYV8xDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8xICU+JQ0KICAgICAgICAgIyBJZiB3ZSB3YW50IHRvIGhhdmUgbiBwb2ludHMNCiAgICAgICAgICMgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzFfbGFiZWwsIEVVTklTYV8xX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8xLCBFVU5JU2FfMV9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fTkRWSSwgY29sb3IgPSBFVU5JUywgDQogICAgICAgICAgIGdyb3VwID0gRVVOSVNhXzFfbGFiZWwpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBFVU5JU2FfMSkpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX05EVkkgLSBzZF9ORFZJLCB5bWF4ID0gbWVhbl9ORFZJICsgc2RfTkRWSSksIA0KICAjd2lkdGggPSAwLjIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNb250aGx5IE5EVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIk5EVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTMSkiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmdnc2F2ZSgNCiAgaGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCAibW9udGhseV9zcGVjdHJvcGhlbm9sb2d5X1MyIiwgIkVVTklTMV9ORFZJLmpwZWciKSwNCiAgZHBpID0gMzAwLCB3aWR0aCA9IDYsIGhlaWdodCA9IDIuNSkNCg0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8xX2xhYmVsLCBFVU5JU2FfMV9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX0VWSSwgY29sb3IgPSBFVU5JUywgDQogICAgICAgICAgIGdyb3VwID0gRVVOSVNhXzFfbGFiZWwpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX0VWSSAtIHNkX0VWSSwgeW1heCA9IG1lYW5fRVZJICsgc2RfRVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBFVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIkVWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMxKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMxX0VWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA2LCBoZWlnaHQgPSAyLjUpDQoNCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzEgJT4lDQogICAgICAgICAjIElmIHdlIHdhbnQgdG8gaGF2ZSBuIHBvaW50cw0KICAgICAgICAgIyBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMV9sYWJlbCwgRVVOSVNhXzFfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzEsIEVVTklTYV8xX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9TQVZJLCBjb2xvciA9IEVVTklTLCANCiAgICAgICAgICAgZ3JvdXAgPSBFVU5JU2FfMV9sYWJlbCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fU0FWSSAtIHNkX1NBVkksIHltYXggPSBtZWFuX1NBVkkgKyBzZF9TQVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBTQVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJTQVZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JUzEpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpnZ3NhdmUoDQogIGhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwgIm1vbnRobHlfc3BlY3Ryb3BoZW5vbG9neV9TMiIsICJFVU5JUzFfU0FWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA2LCBoZWlnaHQgPSAyLjUpDQpgYGANCg0KIyMgRVVOSVMgbGV2ZWwgMg0KDQojIyMgUQ0KDQpgYGB7cn0NCiMgTkRWSQ0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUgDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xID09ICJRIiAmICFpcy5uYShFVU5JU2FfMikpICU+JQ0KICAgICAgICAgIyBSZW1vdmUgdGhvc2Ugd2l0aCBFVU5JUyBsZXZlbCAyIHRoYXQgZG9lcyBub3QgbWF0Y2ggY3VycmVudCBjbGFzc2lmDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8yICE9ICJRYSIgJiBFVU5JU2FfMiAhPSAiUWIiKSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yX2xhYmVsLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX05EVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX05EVkkgLSBzZF9ORFZJLCB5bWF4ID0gbWVhbl9ORFZJICsgc2RfTkRWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgTkRWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiTkRWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1FfTkRWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA3LCBoZWlnaHQgPSAyLjUpDQoNCiMgRVZJDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSANCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgPT0gIlEiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICAjIFJlbW92ZSB0aG9zZSB3aXRoIEVVTklTIGxldmVsIDIgdGhhdCBkb2VzIG5vdCBtYXRjaCBjdXJyZW50IGNsYXNzaWYNCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzIgIT0gIlFhIiAmIEVVTklTYV8yICE9ICJRYiIpICU+JQ0KICAgICAgICAgIyBJZiB3ZSB3YW50IHRvIGhhdmUgbiBwb2ludHMNCiAgICAgICAgICMgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzJfbGFiZWwsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fRVZJLCBjb2xvciA9IEVVTklTLCBncm91cCA9IEVVTklTKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9FVkkgLSBzZF9FVkksIHltYXggPSBtZWFuX0VWSSArIHNkX0VWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgRVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJFVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTMikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmdnc2F2ZSgNCiAgaGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCAibW9udGhseV9zcGVjdHJvcGhlbm9sb2d5X1MyIiwgIkVVTklTMl9RX0VWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA3LCBoZWlnaHQgPSAyLjUpDQoNCiMgU0FWSQ0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUNCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgPT0gIlEiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICAjIFJlbW92ZSB0aG9zZSB3aXRoIEVVTklTIGxldmVsIDIgdGhhdCBkb2VzIG5vdCBtYXRjaCBjdXJyZW50IGNsYXNzaWYNCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzIgIT0gIlFhIiAmIEVVTklTYV8yICE9ICJRYiIpICU+JQ0KICAgICAgICAgIyBJZiB3ZSB3YW50IHRvIGhhdmUgbiBwb2ludHMNCiAgICAgICAgICMgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzJfbGFiZWwsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fU0FWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fU0FWSSAtIHNkX1NBVkksIHltYXggPSBtZWFuX1NBVkkgKyBzZF9TQVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBTQVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJTQVZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JUzIpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpnZ3NhdmUoDQogIGhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwgIm1vbnRobHlfc3BlY3Ryb3BoZW5vbG9neV9TMiIsICJFVU5JUzJfUV9TQVZJLmpwZWciKSwNCiAgZHBpID0gMzAwLCB3aWR0aCA9IDcsIGhlaWdodCA9IDIuNSkNCmBgYA0KDQojIyMgUg0KDQpgYGB7cn0NCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lIA0KICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSA9PSAiUiIgJiAhaXMubmEoRVVOSVNhXzIpKSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yX2xhYmVsLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX05EVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX05EVkkgLSBzZF9ORFZJLCB5bWF4ID0gbWVhbl9ORFZJICsgc2RfTkRWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgTkRWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiTkRWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1JfTkRWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA3LCBoZWlnaHQgPSAyLjUpDQoNCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lIA0KICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSA9PSAiUiIgJiAhaXMubmEoRVVOSVNhXzIpKSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yX2xhYmVsLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX0VWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fRVZJIC0gc2RfRVZJLCB5bWF4ID0gbWVhbl9FVkkgKyBzZF9FVkkpLCANCiAgICAgICAgICAgICAgICAjd2lkdGggPSAwLjIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNb250aGx5IEVWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiRVZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JUzIpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpnZ3NhdmUoDQogIGhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwgIm1vbnRobHlfc3BlY3Ryb3BoZW5vbG9neV9TMiIsICJFVU5JUzJfUl9FVkkuanBlZyIpLA0KICBkcGkgPSAzMDAsIHdpZHRoID0gNywgaGVpZ2h0ID0gMi41KQ0KDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSANCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgPT0gIlIiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICAjIElmIHdlIHdhbnQgdG8gaGF2ZSBuIHBvaW50cw0KICAgICAgICAgIyBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMl9sYWJlbCwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9TQVZJLCBjb2xvciA9IEVVTklTLCBncm91cCA9IEVVTklTKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9TQVZJIC0gc2RfU0FWSSwgeW1heCA9IG1lYW5fU0FWSSArIHNkX1NBVkkpLCANCiAgICAgICAgICAgICAgICAjd2lkdGggPSAwLjIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNb250aGx5IFNBVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIlNBVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTMikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmdnc2F2ZSgNCiAgaGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCAibW9udGhseV9zcGVjdHJvcGhlbm9sb2d5X1MyIiwgIkVVTklTMl9SX1NBVkkuanBlZyIpLA0KICBkcGkgPSAzMDAsIHdpZHRoID0gNywgaGVpZ2h0ID0gMi41KQ0KYGBgDQoNCiMjIyBTDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUgDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xID09ICJTIiAmICFpcy5uYShFVU5JU2FfMikpICU+JQ0KICAgICAgICAgIyBSZW1vdmUgdGhvc2Ugd2l0aCBFVU5JUyBsZXZlbCAyIHRoYXQgZG9lcyBub3QgbWF0Y2ggY3VycmVudCBjbGFzc2lmDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8yICE9ICJTYSIgJiBFVU5JU2FfMiAhPSAiU2IiKSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yX2xhYmVsLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX05EVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX05EVkkgLSBzZF9ORFZJLCB5bWF4ID0gbWVhbl9ORFZJICsgc2RfTkRWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgTkRWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiTkRWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1NfTkRWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA4LCBoZWlnaHQgPSAyLjUpDQoNCmdncGxvdChkYXRhX21vbnRobHlfRVVOSVNhXzIgJT4lIA0KICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMSA9PSAiUyIgJiAhaXMubmEoRVVOSVNhXzIpKSAlPiUNCiAgICAgICAgICMgUmVtb3ZlIHRob3NlIHdpdGggRVVOSVMgbGV2ZWwgMiB0aGF0IGRvZXMgbm90IG1hdGNoIGN1cnJlbnQgY2xhc3NpZg0KICAgICAgICAgZHBseXI6OmZpbHRlcihFVU5JU2FfMiAhPSAiU2EiICYgRVVOSVNhXzIgIT0gIlNiIikgJT4lDQogICAgICAgICAjIElmIHdlIHdhbnQgdG8gaGF2ZSBuIHBvaW50cw0KICAgICAgICAgIyBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMl9sYWJlbCwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9FVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX0VWSSAtIHNkX0VWSSwgeW1heCA9IG1lYW5fRVZJICsgc2RfRVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBFVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIkVWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1NfRVZJLmpwZWciKSwNCiAgZHBpID0gMzAwLCB3aWR0aCA9IDgsIGhlaWdodCA9IDIuNSkNCg0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUgDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xID09ICJTIiAmICFpcy5uYShFVU5JU2FfMikpICU+JQ0KICAgICAgICAgIyBSZW1vdmUgdGhvc2Ugd2l0aCBFVU5JUyBsZXZlbCAyIHRoYXQgZG9lcyBub3QgbWF0Y2ggY3VycmVudCBjbGFzc2lmDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8yICE9ICJTYSIgJiBFVU5JU2FfMiAhPSAiU2IiKSAlPiUNCiAgICAgICAgICMgSWYgd2Ugd2FudCB0byBoYXZlIG4gcG9pbnRzDQogICAgICAgICAjIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yX2xhYmVsLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgICBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgIGFlcyh4ID0gbW9udGgsIHkgPSBtZWFuX1NBVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX1NBVkkgLSBzZF9TQVZJLCB5bWF4ID0gbWVhbl9TQVZJICsgc2RfU0FWSSksIA0KICAgICAgICAgICAgICAgICN3aWR0aCA9IDAuMikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vbnRobHkgU0FWSSBieSBIYWJpdGF0IFR5cGUiLA0KICAgIHggPSAiTW9udGgiLA0KICAgIHkgPSAiU0FWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1NfU0FWSS5qcGVnIiksDQogIGRwaSA9IDMwMCwgd2lkdGggPSA4LCBoZWlnaHQgPSAyLjUpDQpgYGANCg0KIyMjIFQNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSANCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgPT0gIlQiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICAjIElmIHdlIHdhbnQgdG8gaGF2ZSBuIHBvaW50cw0KICAgICAgICAgIyBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMl9sYWJlbCwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9ORFZJLCBjb2xvciA9IEVVTklTLCBncm91cCA9IEVVTklTKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoKSArDQogICNnZW9tKGFlcyh5bWluID0gbWVhbl9ORFZJIC0gc2RfTkRWSSwgeW1heCA9IG1lYW5fTkRWSSArIHNkX05EVkkpLCANCiAgICAgICAgICAgICAgICAjd2lkdGggPSAwLjIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNb250aGx5IE5EVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIk5EVkkiLA0KICAgIGNvbG9yID0gIkhhYml0YXQgKEVVTklTMikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmdnc2F2ZSgNCiAgaGVyZSgib3V0cHV0IiwgImZpZ3VyZXMiLCAibW9udGhseV9zcGVjdHJvcGhlbm9sb2d5X1MyIiwgIkVVTklTMl9UX05EVkkuanBlZyIpLA0KICBkcGkgPSAzMDAsIHdpZHRoID0gNiwgaGVpZ2h0ID0gMi41KQ0KDQpnZ3Bsb3QoZGF0YV9tb250aGx5X0VVTklTYV8yICU+JSANCiAgICAgICAgIGRwbHlyOjpmaWx0ZXIoRVVOSVNhXzEgPT0gIlQiICYgIWlzLm5hKEVVTklTYV8yKSkgJT4lDQogICAgICAgICAjIElmIHdlIHdhbnQgdG8gaGF2ZSBuIHBvaW50cw0KICAgICAgICAgIyBtdXRhdGUoRVVOSVMgPSBwYXN0ZShFVU5JU2FfMl9sYWJlbCwgRVVOSVNhXzJfZGVzY3IsIHNlcCA9ICIgLSAiKSksIA0KICAgICAgICAgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzIsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICBhZXMoeCA9IG1vbnRoLCB5ID0gbWVhbl9FVkksIGNvbG9yID0gRVVOSVMsIGdyb3VwID0gRVVOSVMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgI2dlb20oYWVzKHltaW4gPSBtZWFuX0VWSSAtIHNkX0VWSSwgeW1heCA9IG1lYW5fRVZJICsgc2RfRVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBFVkkgYnkgSGFiaXRhdCBUeXBlIiwNCiAgICB4ID0gIk1vbnRoIiwNCiAgICB5ID0gIkVWSSIsDQogICAgY29sb3IgPSAiSGFiaXRhdCAoRVVOSVMyKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KZ2dzYXZlKA0KICBoZXJlKCJvdXRwdXQiLCAiZmlndXJlcyIsICJtb250aGx5X3NwZWN0cm9waGVub2xvZ3lfUzIiLCAiRVVOSVMyX1RfRVZJLmpwZWciKSwNCiAgZHBpID0gMzAwLCB3aWR0aCA9IDYsIGhlaWdodCA9IDIuNSkNCg0KZ2dwbG90KGRhdGFfbW9udGhseV9FVU5JU2FfMiAlPiUgDQogICAgICAgICBkcGx5cjo6ZmlsdGVyKEVVTklTYV8xID09ICJUIiAmICFpcy5uYShFVU5JU2FfMikpICU+JQ0KICAgICAgICAgIyBJZiB3ZSB3YW50IHRvIGhhdmUgbiBwb2ludHMNCiAgICAgICAgICMgbXV0YXRlKEVVTklTID0gcGFzdGUoRVVOSVNhXzJfbGFiZWwsIEVVTklTYV8yX2Rlc2NyLCBzZXAgPSAiIC0gIikpLCANCiAgICAgICAgIG11dGF0ZShFVU5JUyA9IHBhc3RlKEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgc2VwID0gIiAtICIpKSwgDQogICAgICAgYWVzKHggPSBtb250aCwgeSA9IG1lYW5fU0FWSSwgY29sb3IgPSBFVU5JUywgZ3JvdXAgPSBFVU5JUykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICAjZ2VvbShhZXMoeW1pbiA9IG1lYW5fU0FWSSAtIHNkX1NBVkksIHltYXggPSBtZWFuX1NBVkkgKyBzZF9TQVZJKSwgDQogICAgICAgICAgICAgICAgI3dpZHRoID0gMC4yKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTW9udGhseSBTQVZJIGJ5IEhhYml0YXQgVHlwZSIsDQogICAgeCA9ICJNb250aCIsDQogICAgeSA9ICJTQVZJIiwNCiAgICBjb2xvciA9ICJIYWJpdGF0IChFVU5JUzIpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpnZ3NhdmUoDQogIGhlcmUoIm91dHB1dCIsICJmaWd1cmVzIiwgIm1vbnRobHlfc3BlY3Ryb3BoZW5vbG9neV9TMiIsICJFVU5JUzJfVF9TQVZJLmpwZWciKSwNCiAgZHBpID0gMzAwLCB3aWR0aCA9IDYsIGhlaWdodCA9IDIuNSkNCmBgYA0KDQojIENhbGN1bGF0ZSBvdGhlciBwaGVub2xvZ2ljYWwgbWV0cmljcw0KDQpgYGB7cn0NCmZpbmFsX1JTX2RhdGEgPC0gZmluYWxfUlNfZGF0YSAlPiUNCiAgbXV0YXRlKA0KICAgICMgd2l0aCBzbG9wZSBtZXRob2QNCiAgICAjIEdyb3dpbmcgc2Vhc29uIGR1cmF0aW9uDQogICAgTkRWSV9zbG9wZV9nc2QgPSBORFZJX2Vvc19zbG9wZV9kb3kgLSBORFZJX3Nvc19zbG9wZV9kb3ksDQogICAgRVZJX3Nsb3BlX2dzZCA9IE5EVklfZW9zX3Nsb3BlX2RveSAtIE5EVklfc29zX3Nsb3BlX2RveSwNCiAgICBTQVZJX3Nsb3BlX2dzZCA9IFNBVklfZW9zX3Nsb3BlX2RveSAtIFNBVklfc29zX3Nsb3BlX2RveSwNCiAgICAjIERpZmZlcmVuY2UgaW4gdmFsdWUgYmV0d2VlbiBwb3MgYW5kIHNvcw0KICAgIE5EVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlID0gTkRWSV9wb3NfdmFsdWUgLSBORFZJX3Nvc19zbG9wZV92YWx1ZSwNCiAgICBFVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlID0gRVZJX3Bvc192YWx1ZSAtIEVWSV9zb3Nfc2xvcGVfdmFsdWUsDQogICAgU0FWSV9kaWZmX3Bvc19zb3Nfc2xvcGVfdmFsdWUgPSBTQVZJX3Bvc192YWx1ZSAtIFNBVklfc29zX3Nsb3BlX3ZhbHVlLA0KICAgICMgRGlmZmVyZW5jZSBpbiB2YWx1ZSBiZXR3ZWVuIHBvcyBhbmQgZW9zDQogICAgTkRWSV9kaWZmX3Bvc19lb3Nfc2xvcGVfdmFsdWUgPSBORFZJX3Bvc192YWx1ZSAtIE5EVklfZW9zX3Nsb3BlX3ZhbHVlLA0KICAgIEVWSV9kaWZmX3Bvc19lb3Nfc2xvcGVfdmFsdWUgPSBFVklfcG9zX3ZhbHVlIC0gRVZJX2Vvc19zbG9wZV92YWx1ZSwNCiAgICBTQVZJX2RpZmZfcG9zX2Vvc19zbG9wZV92YWx1ZSA9IFNBVklfcG9zX3ZhbHVlIC0gU0FWSV9lb3Nfc2xvcGVfdmFsdWUsDQogICAgIyBEaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIHBvcyBhbmQgc29zDQogICAgTkRWSV9kaWZmX3Bvc19zb3Nfc2xvcGVfZG95ID0gTkRWSV9wb3NfZG95IC0gTkRWSV9zb3Nfc2xvcGVfZG95LA0KICAgIEVWSV9kaWZmX3Bvc19zb3Nfc2xvcGVfZG95ID0gRVZJX3Bvc19kb3kgLSBFVklfc29zX3Nsb3BlX2RveSwNCiAgICBTQVZJX2RpZmZfcG9zX3Nvc19zbG9wZV9kb3kgPSBTQVZJX3Bvc19kb3kgLSBTQVZJX3Nvc19zbG9wZV9kb3ksDQogICAgIyBBYnNvbHV0ZSBkaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIHBvcyBhbmQgZW9zDQogICAgTkRWSV9kaWZmX3Bvc19lb3Nfc2xvcGVfZG95ID0gYWJzKE5EVklfcG9zX2RveSAtIE5EVklfZW9zX3Nsb3BlX2RveSksDQogICAgRVZJX2RpZmZfcG9zX2Vvc19zbG9wZV9kb3kgPSBhYnMoRVZJX3Bvc19kb3kgLSBFVklfZW9zX3Nsb3BlX2RveSksDQogICAgU0FWSV9kaWZmX3Bvc19lb3Nfc2xvcGVfZG95ID0gYWJzKFNBVklfcG9zX2RveSAtIFNBVklfZW9zX3Nsb3BlX2RveSksDQogICAgIyBXaXRoIHRocmVzaG9sZCBtZXRob2QNCiAgICAjIEdyb3dpbmcgc2Vhc29uIGR1cmF0aW9uDQogICAgTkRWSV90aHJlc2hvbGRfZ3NkID0gTkRWSV9lb3NfdGhyZXNob2xkX2RveSAtIE5EVklfc29zX3RocmVzaG9sZF9kb3ksDQogICAgRVZJX3RocmVzaG9sZF9nc2QgPSBORFZJX2Vvc190aHJlc2hvbGRfZG95IC0gTkRWSV9zb3NfdGhyZXNob2xkX2RveSwNCiAgICBTQVZJX3RocmVzaG9sZF9nc2QgPSBTQVZJX2Vvc190aHJlc2hvbGRfZG95IC0gU0FWSV9zb3NfdGhyZXNob2xkX2RveSwNCiAgICAjIERpZmZlcmVuY2UgaW4gdmFsdWUgYmV0d2VlbiBwb3MgYW5kIHNvcw0KICAgIE5EVklfZGlmZl9wb3Nfc29zX3RocmVzaG9sZF92YWx1ZSA9DQogICAgICBORFZJX3Bvc192YWx1ZSAtIE5EVklfc29zX3RocmVzaG9sZF92YWx1ZSwNCiAgICBFVklfZGlmZl9wb3Nfc29zX3RocmVzaG9sZF92YWx1ZSA9IA0KICAgICAgRVZJX3Bvc192YWx1ZSAtIEVWSV9zb3NfdGhyZXNob2xkX3ZhbHVlLA0KICAgIFNBVklfZGlmZl9wb3Nfc29zX3RocmVzaG9sZF92YWx1ZSA9IA0KICAgICAgU0FWSV9wb3NfdmFsdWUgLSBTQVZJX3Nvc190aHJlc2hvbGRfdmFsdWUsDQogICAgIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBlb3MNCiAgICBORFZJX2RpZmZfcG9zX2Vvc190aHJlc2hvbGRfdmFsdWUgPQ0KICAgICAgTkRWSV9wb3NfdmFsdWUgLSBORFZJX2Vvc190aHJlc2hvbGRfdmFsdWUsDQogICAgRVZJX2RpZmZfcG9zX2Vvc190aHJlc2hvbGRfdmFsdWUgPSANCiAgICAgIEVWSV9wb3NfdmFsdWUgLSBFVklfZW9zX3RocmVzaG9sZF92YWx1ZSwNCiAgICBTQVZJX2RpZmZfcG9zX2Vvc190aHJlc2hvbGRfdmFsdWUgPSANCiAgICAgIFNBVklfcG9zX3ZhbHVlIC0gU0FWSV9lb3NfdGhyZXNob2xkX3ZhbHVlLA0KICAgICMgRGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBwb3MgYW5kIHNvcw0KICAgIE5EVklfZGlmZl9wb3Nfc29zX3RocmVzaG9sZF9kb3kgPSBORFZJX3Bvc19kb3kgLSBORFZJX3Nvc190aHJlc2hvbGRfZG95LA0KICAgIEVWSV9kaWZmX3Bvc19zb3NfdGhyZXNob2xkX2RveSA9IEVWSV9wb3NfZG95IC0gRVZJX3Nvc190aHJlc2hvbGRfZG95LA0KICAgIFNBVklfZGlmZl9wb3Nfc29zX3RocmVzaG9sZF9kb3kgPSBTQVZJX3Bvc19kb3kgLSBTQVZJX3Nvc190aHJlc2hvbGRfZG95LA0KICAgICMgQWJzb2x1dGUgZGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBwb3MgYW5kIGVvcw0KICAgIE5EVklfZGlmZl9wb3NfZW9zX3RocmVzaG9sZF9kb3kgPSANCiAgICAgIGFicyhORFZJX3Bvc19kb3kgLSBORFZJX2Vvc190aHJlc2hvbGRfZG95KSwNCiAgICBFVklfZGlmZl9wb3NfZW9zX3RocmVzaG9sZF9kb3kgPSANCiAgICAgIGFicyhFVklfcG9zX2RveSAtIEVWSV9lb3NfdGhyZXNob2xkX2RveSksDQogICAgU0FWSV9kaWZmX3Bvc19lb3NfdGhyZXNob2xkX2RveSA9IA0KICAgICAgYWJzKFNBVklfcG9zX2RveSAtIFNBVklfZW9zX3RocmVzaG9sZF9kb3kpLA0KICAgICMgV2l0aCBtb250aHMgbWV0aG9kDQogICAgIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBtYXJjaA0KICAgIE5EVklfZGlmZl9wb3NfbWFyY2hfdmFsdWUgPSBORFZJX3Bvc192YWx1ZSAtIE5EVklfYXZnX3ZhbHVlXzAzLA0KICAgIEVWSV9kaWZmX3Bvc19tYXJjaF92YWx1ZSA9IEVWSV9wb3NfdmFsdWUgLSBFVklfYXZnX3ZhbHVlXzAzLA0KICAgIFNBVklfZGlmZl9wb3NfbWFyY2hfdmFsdWUgPSBTQVZJX3Bvc192YWx1ZSAtIFNBVklfYXZnX3ZhbHVlXzAzLA0KICAgICMgRGlmZmVyZW5jZSBpbiB2YWx1ZSBiZXR3ZWVuIHBvcyBhbmQgb2N0DQogICAgTkRWSV9kaWZmX3Bvc19vY3RfdmFsdWUgPSBORFZJX3Bvc192YWx1ZSAtIE5EVklfYXZnX3ZhbHVlXzEwLA0KICAgIEVWSV9kaWZmX3Bvc19vY3RfdmFsdWUgPSBFVklfcG9zX3ZhbHVlIC0gRVZJX2F2Z192YWx1ZV8xMCwNCiAgICBTQVZJX2RpZmZfcG9zX29jdF92YWx1ZSA9IFNBVklfcG9zX3ZhbHVlIC0gU0FWSV9hdmdfdmFsdWVfMTAsDQogICAgIyBEaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIHBvcyBhbmQgbWFyY2gNCiAgICBORFZJX2RpZmZfcG9zX21hcmNoX2RveSA9IE5EVklfcG9zX2RveSAtIDc1LCANCiAgICBFVklfZGlmZl9wb3NfbWFyY2hfZG95ID0gRVZJX3Bvc19kb3kgLSA3NSwNCiAgICBTQVZJX2RpZmZfcG9zX21hcmNoX2RveSA9IFNBVklfcG9zX2RveSAtIDc1LA0KICAgICMgRGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBwb3MgYW5kIG9jdA0KICAgIE5EVklfZGlmZl9wb3Nfb2N0X2RveSA9IGFicyhORFZJX3Bvc19kb3kgLSAyODUpLA0KICAgIEVWSV9kaWZmX3Bvc19vY3RfZG95ID0gYWJzKEVWSV9wb3NfZG95IC0gMjg1KSwNCiAgICBTQVZJX2RpZmZfcG9zX29jdF9kb3kgPSBhYnMoU0FWSV9wb3NfZG95IC0gMjg1KQ0KICApDQpgYGANCg0KIyMgQ2hlY2tzDQoNCiMjIyBTbG9wZSBtZXRob2QNCg0KYGBge3J9DQojIEdyb3dpbmcgc2Vhc29uIGR1cmF0aW9uIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JSANCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfc2xvcGVfZ3NkIDw9IDAgfCBFVklfc2xvcGVfZ3NkIDw9IDAgfCANCiAgICAgICAgICAgICAgICAgICAgICAgU0FWSV9zbG9wZV9nc2QgPD0gMCkpDQojIERpZmZlcmVuY2UgaW4gdmFsdWUgYmV0d2VlbiBwb3MgYW5kIHNvcyBzaG91bGQgYmUgcG9zaXRpdmUNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlIDw9IDApKQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX2RpZmZfcG9zX3Nvc19zbG9wZV92YWx1ZSA8PSAwKSkNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKFNBVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlIDw9IDApKQ0KIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBlb3Mgc2hvdWxkIGJlIHBvc2l0aXZlDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihORFZJX2RpZmZfcG9zX2Vvc19zbG9wZV92YWx1ZSA8PSAwKSkNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKEVWSV9kaWZmX3Bvc19lb3Nfc2xvcGVfdmFsdWUgPD0gMCkpDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihTQVZJX2RpZmZfcG9zX2Vvc19zbG9wZV92YWx1ZSA8PSAwKSkNCiMgRGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBwb3MgYW5kIHNvcyBzaG91bGQgYmUgcG9zaXRpdmUNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfZGlmZl9wb3Nfc29zX3Nsb3BlX2RveSA8PSAwIHwNCiAgICAgICAgICAgICAgICAgICAgICAgRVZJX2RpZmZfcG9zX3Nvc19zbG9wZV9kb3kgPD0gMCB8DQogICAgICAgICAgICAgICAgICAgICAgIFNBVklfZGlmZl9wb3Nfc29zX3Nsb3BlX2RveSA8PSAwKSkNCiMgRGlmZmVyZW5jZSBpbiBkb3kgYmV0d2VlbiBlb3MgYW5kIHBvcyBzaG91bGQgYmUgcG9zaXRpdmUNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfZGlmZl9wb3NfZW9zX3Nsb3BlX2RveSA8PSAwIHwNCiAgICAgICAgICAgICAgICAgICAgICAgRVZJX2RpZmZfcG9zX2Vvc19zbG9wZV9kb3kgPD0gMCB8DQogICAgICAgICAgICAgICAgICAgICAgIFNBVklfZGlmZl9wb3NfZW9zX3Nsb3BlX2RveSA8PSAwKSkNCmBgYA0KDQojIyMgVGhyZXNob2xkIG1ldGhvZA0KDQpgYGB7cn0NCiMgR3Jvd2luZyBzZWFzb24gZHVyYXRpb24gc2hvdWxkIGJlIHBvc2l0aXZlDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lIA0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV90aHJlc2hvbGRfZ3NkIDw9IDAgfCBFVklfdGhyZXNob2xkX2dzZCA8PSAwIHwgDQogICAgICAgICAgICAgICAgICAgICAgIFNBVklfdGhyZXNob2xkX2dzZCA8PSAwKSkNCiMgRGlmZmVyZW5jZSBpbiB2YWx1ZSBiZXR3ZWVuIHBvcyBhbmQgc29zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV9kaWZmX3Bvc19zb3NfdGhyZXNob2xkX3ZhbHVlIDw9IDApKQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoRVZJX2RpZmZfcG9zX3Nvc190aHJlc2hvbGRfdmFsdWUgPD0gMCkpDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihTQVZJX2RpZmZfcG9zX3Nvc190aHJlc2hvbGRfdmFsdWUgPD0gMCkpDQojIERpZmZlcmVuY2UgaW4gdmFsdWUgYmV0d2VlbiBwb3MgYW5kIGVvcyBzaG91bGQgYmUgcG9zaXRpdmUNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKE5EVklfZGlmZl9wb3NfZW9zX3RocmVzaG9sZF92YWx1ZSA8PSAwKSkNCm5yb3coZmluYWxfUlNfZGF0YSAlPiUNCiAgICAgICBkcGx5cjo6ZmlsdGVyKEVWSV9kaWZmX3Bvc19lb3NfdGhyZXNob2xkX3ZhbHVlIDw9IDApKQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoU0FWSV9kaWZmX3Bvc19lb3NfdGhyZXNob2xkX3ZhbHVlIDw9IDApKQ0KIyBEaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIHBvcyBhbmQgc29zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV9kaWZmX3Bvc19zb3NfdGhyZXNob2xkX2RveSA8PSAwIHwNCiAgICAgICAgICAgICAgICAgICAgICAgRVZJX2RpZmZfcG9zX3Nvc190aHJlc2hvbGRfZG95IDw9IDAgfA0KICAgICAgICAgICAgICAgICAgICAgICBTQVZJX2RpZmZfcG9zX3Nvc190aHJlc2hvbGRfZG95IDw9IDApKQ0KIyBEaWZmZXJlbmNlIGluIGRveSBiZXR3ZWVuIGVvcyBhbmQgcG9zIHNob3VsZCBiZSBwb3NpdGl2ZQ0KbnJvdyhmaW5hbF9SU19kYXRhICU+JQ0KICAgICAgIGRwbHlyOjpmaWx0ZXIoTkRWSV9kaWZmX3Bvc19lb3NfdGhyZXNob2xkX2RveSA8PSAwIHwNCiAgICAgICAgICAgICAgICAgICAgICAgRVZJX2RpZmZfcG9zX2Vvc190aHJlc2hvbGRfZG95IDw9IDAgfA0KICAgICAgICAgICAgICAgICAgICAgICBTQVZJX2RpZmZfcG9zX2Vvc190aHJlc2hvbGRfZG95IDw9IDApKQ0KYGBgDQoNCiMgSEVSRTogUnVuLiBEZXRlY3QgbnVtYmVyIG9mIHBlYWtzIGluIHNtb290aGVkIGN1cnZlcw0KDQpgYGB7cn0NCiMgU2V0IHVwIHBhcmFsbGVsIHBsYW4NCnBsYW4obXVsdGlzZXNzaW9uLCB3b3JrZXJzID0gbWluKHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpIC0gMSkpDQoNCiMgRGVmaW5lIHBlYWstY291bnRpbmcgZnVuY3Rpb24NCmNvdW50X3BlYWtzIDwtIGZ1bmN0aW9uKGRmKSB7DQogICMgQ29udmVydCAxRCBhcnJheSBjb2x1bW4gdG8gbnVtZXJpYyB2ZWN0b3INCiAgeSA8LSBhcy5udW1lcmljKGRmJHZhbHVlKQ0KICANCiAgcGVha3MgPC0gZmluZHBlYWtzKHksIG1pbnBlYWtkaXN0YW5jZSA9IDMwLCB0aHJlc2hvbGQgPSAwLjAyKQ0KICANCiAgdGliYmxlKA0KICAgIFBsb3RPYnNlcnZhdGlvbklEID0gdW5pcXVlKGRmJFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICBpbmRleCA9IHVuaXF1ZShkZiRpbmRleCksDQogICAgbnVtX3BlYWtzID0gaWYgKCFpcy5udWxsKHBlYWtzKSkgbnJvdyhwZWFrcykgZWxzZSAwDQogICkNCn0NCg0KIyBBcHBseSBwZWFrIGNvdW50aW5nIGluIHBhcmFsbGVsDQpwZWFrX2NvdW50cyA8LSBHQU1fZGF0YSAlPiUNCiAgZmlsdGVyKGZpdF90eXBlID09ICJpdGVyXzMiKSAlPiUNCiAgYXJyYW5nZShET1kpICU+JQ0KICBncm91cF9ieShQbG90T2JzZXJ2YXRpb25JRCwgaW5kZXgpICU+JQ0KICBuZXN0KCkgJT4lDQogIG11dGF0ZShyZXN1bHQgPSBmdXR1cmVfbWFwKGRhdGEsIGNvdW50X3BlYWtzLCAucHJvZ3Jlc3MgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAub3B0aW9ucyA9IGZ1cnJyX29wdGlvbnMoc2NoZWR1bGluZyA9IEluZikpKSAlPiUNCiAgc2VsZWN0KC1kYXRhKSAlPiUNCiAgdW5uZXN0KHJlc3VsdCkNCg0KIyBTdW1tYXJpemUgcmVzdWx0DQpwZWFrX2NvdW50c19zdW1tYXJ5IDwtIHBlYWtfY291bnRzICU+JSBjb3VudChpbmRleCwgbnVtX3BlYWtzKQ0KYGBgDQoNCiMgSEVSRTogc2F2ZQ0KDQpgYGB7cn0NCiMgIyBGdW5jdGlvbiB0byBjb3VudCBwZWFrcyBmb3IgZWFjaCBQbG90T2JzZXJ2YXRpb25JRA0KIyBjb3VudF9wZWFrcyA8LSBmdW5jdGlvbihkZikgew0KIyAgIHkgPC0gZGYkdmFsdWUNCiMgICBwZWFrcyA8LSBmaW5kcGVha3MoeSwgDQojICAgICAgICAgICAgICAgICAgICAgICMgTWluaW11bSBudW1iZXIgb2YgaW5kaWNlcyAoZS5nLiwgRE9ZIHN0ZXBzKQ0KIyAgICAgICAgICAgICAgICAgICAgICAjIGJldHdlZW4gdHdvIHBlYWtzDQojICAgICAgICAgICAgICAgICAgICAgIG1pbnBlYWtkaXN0YW5jZSA9IDMwLCANCiMgICAgICAgICAgICAgICAgICAgICAgIyBNaW5pbXVtIHZlcnRpY2FsIGRpZmZlcmVuY2UgYmV0d2VlbiBhIHBlYWsNCiMgICAgICAgICAgICAgICAgICAgICAgIyBhbmQgaXRzIHN1cnJvdW5kaW5nIHZhbHVlDQojICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZCA9IDAuMDIpDQojICAgbnVtX3BlYWtzIDwtIGlmICghaXMubnVsbChwZWFrcykpIG5yb3cocGVha3MpIGVsc2UgMA0KIyAgIHJldHVybih0aWJibGUoUGxvdE9ic2VydmF0aW9uSUQgPSB1bmlxdWUoZGYkUGxvdE9ic2VydmF0aW9uSUQpLA0KIyAgICAgICAgICAgICAgICAgbnVtX3BlYWtzID0gbnVtX3BlYWtzKSkNCiMgfQ0KIyANCiMgIyBBcHBseSB0byBlYWNoIGdyb3VwDQojIHBlYWtfY291bnRzIDwtIEdBTV9kYXRhICU+JQ0KIyAgIG11dGF0ZSh2YWx1ZSA9IG1hcF9kYmwodmFsdWUsIDEpKSAlPiUNCiMgICBkcGx5cjo6ZmlsdGVyKGZpdF90eXBlID09ICJpdGVyXzMiKSAlPiUNCiMgICBhcnJhbmdlKERPWSkgJT4lDQojICAgZ3JvdXBfYnkoUGxvdE9ic2VydmF0aW9uSUQsIGluZGV4KSAlPiUNCiMgICBncm91cF9tb2RpZnkofiBjb3VudF9wZWFrcygueCkpICU+JQ0KIyAgIHVuZ3JvdXAoKQ0KIyANCiMgIyBWaWV3IHJlc3VsdA0KIyBwZWFrX2NvdW50cyAlPiUgY291bnQoaW5kZXgsIG51bV9wZWFrcykNCmBgYA0KDQojIyBQbG90IG51bWJlciBvZiBwZWFrcw0KDQpgYGB7cn0NCnBlYWtfY291bnRzICU+JSBjb3VudChpbmRleCwgbnVtX3BlYWtzKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gaW5kZXgsIHkgPSBuLCBmaWxsID0gZmFjdG9yKG51bV9wZWFrcykpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKCkpDQpgYGANCg0KRVZJIGdpdmVzIGxlc3MgcHJvYmxlbXMsIG1heWJlIHVzZSBvbmx5IHRoaXMgb25lPw0KDQojIyBBZGQgbnVtYmVyIG9mIHBlYWtzIHRvIGRhdGENCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZpbmFsX1JTX2RhdGEgJT4lDQogIGxlZnRfam9pbihwZWFrX2NvdW50cyAlPiUNCiAgICAgICAgICAgICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGluZGV4LCB2YWx1ZXNfZnJvbSA9IG51bV9wZWFrcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfZ2x1ZSA9ICJ7aW5kZXh9X3sudmFsdWV9IikpDQpgYGANCg0KIyMgUGxvdCBmaXQgYW5kIG1vbWVudHMgZm9yIFBsb3RPYnNlcnZhdGlvbklEcyB3aXRoIHplcm8gcGVha3MNCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCiMgR2V0IHVuaXF1ZSBQbG90T2JzZXJ2YXRpb25JRHMNCnVuaXF1ZV9pZHMgPC0gZmluYWxfUlNfZGF0YSAlPiUNCiAgZHBseXI6OmZpbHRlcihFVklfbnVtX3BlYWtzID09IDApICU+JQ0KICBwdWxsKFBsb3RPYnNlcnZhdGlvbklEKSAlPiUNCiAgYXMuY2hhcmFjdGVyKCkNCg0KIyBDcmVhdGUgYW5kIHN0b3JlIHBsb3RzIGluIGEgbGlzdA0KcGxvdHNfRVZJXzBwZWFrcyA8LSBtYXAodW5pcXVlX2lkc1sxOjUwXSwgZnVuY3Rpb24oaWQpIHsNCiAgcGxvdF9kYXRhIDwtIEdBTV9kYXRhICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIoYXMuY2hhcmFjdGVyKFBsb3RPYnNlcnZhdGlvbklEKSA9PSBpZCkgJT4lDQogICAgZHBseXI6OmZpbHRlcihmaXRfdHlwZSA9PSAib2JzZXJ2ZWQiIHwgZml0X3R5cGUgPT0gIml0ZXJfMyIpDQogIA0KICBnZ3Bsb3QoKSArDQogICAgIyBSYXcgZGF0YSBwb2ludHMNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBkcGx5cjo6ZmlsdGVyKHBsb3RfZGF0YSwgZml0X3R5cGUgPT0gIm9ic2VydmVkIiksIA0KICAgICAgICAgICAgICAgYWVzKHggPSBET1ksIHkgPSB2YWx1ZSksIGFscGhhID0gMC41KSArDQogICAgZ2VvbV9saW5lKGRhdGEgPSBkcGx5cjo6ZmlsdGVyKHBsb3RfZGF0YSwgZml0X3R5cGUgPT0gIml0ZXJfMyIpLA0KICAgICAgICAgICAgICBhZXMoeCA9IERPWSwgeSA9IHZhbHVlKSwgc2l6ZSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICAgIGdlb21fdmxpbmUoZGF0YSA9IGRwbHlyOjpmaWx0ZXIocGxvdF9kYXRhLCBmaXRfdHlwZSA9PSAiaXRlcl8zIiksDQogICAgICAgICAgICAgICBhZXMoeGludGVyY2VwdCA9IHNvcyksDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMC41LCBjb2xvciA9ICJyZWQiKSArDQogICAgZ2VvbV92bGluZShkYXRhID0gZHBseXI6OmZpbHRlcihwbG90X2RhdGEsIGZpdF90eXBlID09ICJpdGVyXzMiKSwNCiAgICAgICAgICAgICAgIGFlcyh4aW50ZXJjZXB0ID0gcG9zKSwNCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gImRvdHRlZCIsIHNpemUgPSAwLjUsIGNvbG9yID0gInJlZCIpICsNCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBkcGx5cjo6ZmlsdGVyKHBsb3RfZGF0YSwgZml0X3R5cGUgPT0gIml0ZXJfMyIpLA0KICAgICAgICAgICAgICAgYWVzKHhpbnRlcmNlcHQgPSBlb3MpLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuNSwgY29sb3IgPSAicmVkIikgKw0KICAgIGZhY2V0X2dyaWQoY29scyA9IHZhcnMoaW5kZXgpKSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gZ2x1ZTo6Z2x1ZSgiUGxvdE9ic2VydmF0aW9uSUQ6IHtpZH0iKSwNCiAgICAgIHggPSAiRGF5IG9mIFllYXIiLA0KICAgICAgeSA9ICJJbmRleCBWYWx1ZSINCiAgICApICsNCiAgICB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikNCn0pDQoNCiMgTmFtZSB0aGUgbGlzdCBieSBQbG90T2JzZXJ2YXRpb25JRA0KbmFtZXMocGxvdHNfRVZJXzBwZWFrcykgPC0gdW5pcXVlX2lkc1sxOjUwXQ0KDQojIERpc3BsYXkgdGhlIGZpcnN0IHBsb3QNCnByaW50KHBsb3RzX0VWSV8wcGVha3NbWzFdXSkNCmBgYA0KDQojIyBGdXJ0aGVyIGNoZWNrcyAoRVZJKQ0KDQojIyMgU2xvcGUgbWV0aG9kDQoNCmBgYHtyfQ0KIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBzb3Mgc2hvdWxkIGJlIHBvc2l0aXZlDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihFVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlIDw9IDApKQ0KZmluYWxfUlNfZGF0YSAlPiUNCiAgZHBseXI6OmZpbHRlcihFVklfZGlmZl9wb3Nfc29zX3Nsb3BlX3ZhbHVlIDw9IDApICU+JQ0KICBjb3VudChFVklfbnVtX3BlYWtzKQ0KIyBEaWZmZXJlbmNlIGluIHZhbHVlIGJldHdlZW4gcG9zIGFuZCBlb3Mgc2hvdWxkIGJlIHBvc2l0aXZlDQpucm93KGZpbmFsX1JTX2RhdGEgJT4lDQogICAgICAgZHBseXI6OmZpbHRlcihFVklfZGlmZl9wb3NfZW9zX3Nsb3BlX3ZhbHVlIDw9IDApKQ0KZmluYWxfUlNfZGF0YSAlPiUNCiAgZHBseXI6OmZpbHRlcihFVklfZGlmZl9wb3NfZW9zX3Nsb3BlX3ZhbHVlIDw9IDApICU+JQ0KICBjb3VudChFVklfbnVtX3BlYWtzKQ0KYGBgDQoNCiMjIyBUaHJlc2hvbGQgbWV0aG9kDQoNCiMgQWRkIHNvbWUgY29sdW1ucyBuZWVkZWQNCg0KYGBge3J9DQpmaW5hbF9SU19kYXRhIDwtIGZpbmFsX1JTX2RhdGEgJT4lDQogIGxlZnRfam9pbigNCiAgICBkYXRhX1JTX1MyX2JhbmRzX2luZGljZXMgJT4lDQogICAgICBkaXN0aW5jdChQbG90T2JzZXJ2YXRpb25JRCwgeWVhciwgYmlvZ2VvLCB1bml0LCBMY3RubXRoKQ0KICAgICkNCmBgYA0KDQojIEFkZCBjYW5vcHkgaGVpZ2h0IGRhdGENCg0KUmVhZCB0aGUgZGF0YToNCg0KYGBge3J9DQpkYXRhX1JTX0NIIDwtIHJlYWRfY3N2KA0KICAiQzovRGF0YS9NT1RJVkFURS9NT1RJVkFURV9SU19kYXRhL0Nhbm9weV9IZWlnaHRfMW0vRXVyb3BlX3BvaW50c19DYW5vcHlIZWlnaHRfMW0uY3N2IikNCmRiX0V1cm9wYSA8LSByZWFkX2NzdigNCiAgaGVyZSgiLi4iLCAiREJfZmlyc3RfY2hlY2siLCAiZGF0YSIsICJjbGVhbiIsImRiX0V1cm9wYV8yMDI1MDEwNy5jc3YiKQ0KICApDQpgYGANCg0KYGBge3J9DQpkYXRhX1JTX0NIX0lEIDwtIGRiX0V1cm9wYSAlPiUNCiAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBvYnNfdW5pcXVlX2lkKSAlPiUNCiAgcmlnaHRfam9pbihkYXRhX1JTX0NIICU+JQ0KICAgICAgICAgICAgICAjIFJlbmFtZSB0byBiZSBhYmxlIHRvIGpvaW4gb24gdGhpcyBjb2x1bW4NCiAgICAgICAgICAgICAgcmVuYW1lKG9ic191bmlxdWVfaWQgPSBvYnNfdW5pcXVlKSkgJT4lDQogIHNlbGVjdChQbG90T2JzZXJ2YXRpb25JRCwgY2Fub3B5X2hlaWdodCkNCmBgYA0KDQpKb2luOg0KDQpgYGB7cn0NCmZpbmFsX1JTX2RhdGEgPC0gZmluYWxfUlNfZGF0YSAlPiUNCiAgbGVmdF9qb2luKGRhdGFfUlNfQ0hfSUQgJT4lDQogICAgICAgICAgICAgIG11dGF0ZShQbG90T2JzZXJ2YXRpb25JRCA9IGZhY3RvcihQbG90T2JzZXJ2YXRpb25JRCkpKQ0KYGBgDQoNCiMgU2F2ZSB0byBjbGVhbiBkYXRhDQoNCmBgYHtyfQ0Kd3JpdGVfdHN2KGZpbmFsX1JTX2RhdGEsDQogICAgICAgICAgaGVyZSgiZGF0YSIsICJjbGVhbiIsImZpbmFsX1JTX2RhdGFfYmFuZHNfUzJfYWxsXzIwMjUwODA0LmNzdiIpKQ0KYGBgDQoNCiMgU2Vzc2lvbiBpbmZvDQoNCmBgYHtyfQ0Kc2Vzc2lvbkluZm8oKQ0KYGBgDQoNCg==